// 运行模式，会提示一些警告和错误信息
let EDebug = true;

/**
 * 一些默认设置
 */
const EDefaultOptions = {
    /**
     * 缓存中当前登录用户key
     */
    currentUser: 'current_user',
    session: {
        /**
         * 管理员强制退出
         */
        force_logout: 'force_logout',
        /**
         * 在其他地方登录,被踢出
         */
        login_elsewhere: 'login_elsewhere',
        /**
         * 超时失效
         */
        invalid: 'invalid'
    },

    /**
     * DataTable 默认参数
     */
    dataTable: {
        page: {
            /**
             * 默认页大小
             */
            size: 15
        },
        layout: {
            /**
             * 默认高度
             *
             * @return {number}
             */
            height: function () {
                let minHeight = 300;
                // 默认保留300px高度
                let height = EUtil.getViewPort().height - 300;
                return Math.max(height, minHeight);
            }
        }
    }
};

/**
 * css 前缀
 */
const cssPrefix = 'e-';

/**
 * 通用url后缀
 */
const urlSuffix = {
    /**
     * 新增默认访问后缀 (示例: /add、/add/{pId})
     */
    add: 'add/',
    /**
     * 根据id删除数据默认访问后缀 (示例: delete/1)
     */
    deleteById: 'delete/',
    /**
     * 根据id修改数据默认访问后缀 (示例: input/1)
     */
    input: 'input/',
    /**
     * 批量删除默认访问后缀 (示例: /batch/delete/1,2,3)
     * 获取当前表单中已选中表格中的checkbox的值使用[,]拼接,并将其作为参数传入
     */
    // batchDelete: 'batch/delete/',
    /**
     * 保存默认访问后缀 (示例: /save/data)
     * 会将当前所在form中表单元素作为参数传入
     */
    saveData: 'save/data',
    /**
     * 查询默认访问后缀 (示例: /select)
     * 会将当前所在form中表单元素作为参数传入
     */
    select: 'select',
    /**
     * 导出数据默认访问后缀 (示例: /export/data)
     * 会将当前所在form中表单元素作为参数传入
     */
    exportData: 'export/data',

    /**
     * 导入数据默认访问后缀 (示例: /export/data/templateCode)
     */
    importData: 'import/data'
};

/**
 * 默认提示文字
 */
const commonTips = {
    /**
     * 登录验证失败
     */
    unauthorized: '请重新登录',
    /**
     * 已登录，无权访问
     */
    forbidden: '无权访问',
    /**
     * 删除
     */
    delete: {
        title: '确定要删除吗？',
        subtitle: '删除后无法恢复，请谨慎操作',
        success: '数据已删除'
    },
    /**
     * 保存
     */
    save: {
        default: '数据已保存',
        // 如果页面中`#id`的`value`为空则使用此提示
        add: '数据已保存',
        // 如果页面中`#id`的`value`不为空则使用此提示
        update: '数据已保存'
    },
    // 批量操作时未勾选数据
    noIds: '请在下方表格中勾选需要操作的数据后重试',
    success: '成功',
    info: '消息',
    warn: '警告',
    error: '错误',
    fail: '失败'
};

/**
 * HttpStatus 状态码
 */
const httpStatus = {
    success: 200, // 成功
    badRequest: 400, // 无效请求 eg：表单验证
    unauthorized: 401, // 会话失效 eg：登录失败、被踢出，在其他地方登录，管理员踢出
    forbidden: 403, // 已登录，无权访问 eg：无权限
    internalServerError: 500 // 异常
};

/**
 * 响应码，由6位数字构成abbccc
 * a   错误级别 0-成功、1-信息、2-警告、3-错误
 * bb  服务模块代码，请从01开始使用00为公共模块
 * ccc 具体错误代码
 */
const resultCode = {
    length: 6,
    // 错误级别
    level: {
        success: '0',
        info: '1',
        warn: '2',
        error: '3'
    },
    modular: {
        // 公用模块
        common: '00'
    },
    code: {
        // 常见错误代码
        success: '000', // 成功
        badRequest: '400', // 无效请求 eg：表单验证
        unauthorized: '401', // 会话失效 eg：登录失败、被踢出，在其他地方登录，管理员踢出
        forbidden: '403', // 已登录，无权访问 eg：无权限
        internalServerError: '500' // 异常
    }
};
// let basePath = null;

"use strict";

// 全局变量，用于存放一些初始化完成的插件变量，使用key:value方式存储
// 缓存
window.EUtilCache = {};
// 缓存序号
window.EUtilCacheSN = 1;

/**
 * 提供读写缓存数据、获取URL参数、判断设备类型、窗口尺寸、UUID、isXXX、提示、设置等待状态、ajax等公用方法
 */
let EUtil = function () {
    // 窗口大小改变时的回调
    let resizeHandlers = [];

    /**
     * 响应式配置
     */
    let breakpoints = {
        sm: 544, // 小屏幕 - 手机
        md: 768, // 中等屏幕 - pad
        lg: 1024, // 大屏 - 桌面
        xl: 1200, // xl 大屏 - 桌面
        xxl: 1400, // xxl 大屏 - 桌面
        xxxl: 1600 // xxxl 大屏 - 桌面
    };

    /**
     * 处理窗口大小调整事件方法中加入事件防抖，拖动后`250ms`后执行；
     * 你可以通过`EUtil.addResizeHandler(callback)`和`EUtil.removeResizeHandler(callback)`添加或删除回调
     */
    let windowResizeHandler = function () {
        let timeout = null; // 定时器
        let delay = 250; // 延迟时间

        $(window).on('resize', function () {
            clearTimeout(timeout);
            timeout = setTimeout(function () {
                EUtil.runResizeHandlers();
            }, delay);
        });
    };


    return {
        /**
         * 调用`windowResizeHandler`，监听了`window`的`resize`事件
         */
        init: function () {
            windowResizeHandler();
        },
        /**
         * 添加窗口大小调整回调，无需考虑防抖，已经在`windowResizeHandler`统一处理
         *
         * @param callback {function} 回调函数
         */
        addResizeHandler: function (callback) {
            resizeHandlers.push(callback);
        },

        /**
         * 移除窗口大小调整回调
         *
         * @param callback {function} 回调函数
         */
        removeResizeHandler: function (callback) {
            for (let i = 0; i < resizeHandlers.length; i++) {
                if (callback === resizeHandlers[i]) {
                    delete resizeHandlers[i];
                }
            }
        },
        /**
         * 触发窗口大小调整事件，会调用`resizeHandlers`中所有回调
         */
        runResizeHandlers: function () {
            for (let i = 0; i < resizeHandlers.length; i++) {
                if (EUtil.isFunction(resizeHandlers[i])) {
                    resizeHandlers[i].call();
                }
            }
        },
        /**
         * 全局变量，一般用于存放一些与元素绑定的变量
         *
         * @param element {object} html对象或jquery对象
         */
        data: function (element) {
            let $element = $(element);
            // 序号在元素上的属性名
            let snKey = 'e-cache-sn';
            // 序号
            let serialNumber = $element.data(snKey);
            return {
                /**
                 * 根据`key`获取元素变量的`value`，如果没有指定的`key`将返回`null`
                 *
                 * @param key {string} key
                 * @returns {null|*}
                 */
                get: function (key) {
                    // 如果为多个元素或者data-e-cache为空，无法获取对象，直接返回null
                    if ($element.length !== 1 || EUtil.isBlank(serialNumber)) {
                        return null;
                    }
                    return EUtilCache[serialNumber][key];
                },
                /**
                 * 向全局变量中添加值
                 *
                 * @param key {string} key
                 * @param value {object} value
                 */
                set: function (key, value) {
                    // 如果为多个元素，直接返回null
                    if ($element.length !== 1) {
                        return null;
                    }
                    if (EUtil.isBlank(serialNumber)) {
                        serialNumber = EUtilCacheSN++;
                        $element.attr('data-' + snKey, serialNumber);
                        EUtilCache[serialNumber] = {};
                    }
                    EUtilCache[serialNumber][key] = value;
                },
                /**
                 * 查找元素变量中是否有指定`key`
                 *
                 * @param key {string} key
                 * @returns {boolean} true/false
                 */
                has: function (key) {
                    // 如果为多个元素或者data-e-cache为空，无法获取对象，直接返回false
                    if ($element.length !== 1 || EUtil.isBlank(serialNumber)) {
                        return false;
                    }
                    return EUtilCache[serialNumber] && EUtilCache[serialNumber][key];
                },
                /**
                 * 根据`key`删除元素缓存的`value`
                 *
                 * @param key {string} key
                 */
                remove: function (key) {
                    // 如果为多个元素或者data-e-cache为空，无法获取对象，直接返回null
                    if ($element.length === 1 && EUtil.isNotBlank(serialNumber)) {
                        delete EUtilCache[serialNumber][key];
                    }
                }
            }
        },
        /**
         * 根据参数名称从url中获取参数值，如没有返回`null`
         *
         * @param {string} paramName 参数名
         * @returns {string|null}
         */
        getURLParam: function (paramName) {
            let searchString = window.location.search.substring(1),
                i, val, params = searchString.split("&");

            for (i = 0; i < params.length; i++) {
                val = params[i].split("=");
                if (val[0] === paramName) {
                    return unescape(val[1]);
                }
            }

            return null;
        },

        /**
         * 根据窗口判断是否属于移动设备(宽度小于lg)
         *
         * @returns {boolean} true/false
         */
        isMobileDevice: function () {
            return this.getViewPort().width < this.getBreakpoint('lg');
        },

        /**
         * 根据窗口判断是否属于桌面设备(宽度大于lg)
         *
         * @returns {boolean} true/false
         */
        isDesktopDevice: function () {
            return !EUtil.isMobileDevice();
        },

        /**
         * 获取浏览器窗口尺寸
         *
         * @returns {object} eg: {width: 1920, height: 1080}
         */
        getViewPort: function () {
            let e = window,
                a = 'inner';
            if (!('innerWidth' in window)) {
                a = 'client';
                e = document.documentElement || document.body;
            }

            return {
                width: e[a + 'Width'],
                height: e[a + 'Height']
            };
        },

        /**
         * 检查当前设备是否为指定类型
         *
         * @param {string} mode 类型(e.g: desktop, desktop-and-tablet, tablet, tablet-and-mobile, mobile)
         * @returns {boolean} true/false
         */
        isInResponsiveRange: function (mode) {
            // 浏览器宽度
            let breakpoint = this.getViewPort().width;

            if (mode === 'desktop' && breakpoint >= (this.getBreakpoint('lg') + 1)) {
                return true;
            } else if (mode === 'tablet' && (breakpoint >= (this.getBreakpoint('md') + 1) && breakpoint < this.getBreakpoint('lg'))) {
                return true;
            } else if (mode === 'mobile' && breakpoint <= this.getBreakpoint('md')) {
                return true;
            } else if (mode === 'desktop-and-tablet' && breakpoint >= (this.getBreakpoint('md') + 1)) {
                return true;
            } else if (mode === 'tablet-and-mobile' && breakpoint <= this.getBreakpoint('lg')) {
                return true;
            } else if (mode === 'minimal-desktop-and-below' && breakpoint <= this.getBreakpoint('xl')) {
                return true;
            } else if (mode === 'big-screen' && breakpoint <= this.getBreakpoint('xxl')) {
                return true;
            } else if (mode === 'super-screen' && breakpoint <= this.getBreakpoint('xxxl')) {
                return true;
            }

            return false;
        },
        /**
         * 检查是否为IE系列浏览器，如果是则返回版本，如果不是返回`false`
         *
         * @returns {boolean|number}
         */
        detectIE: function () {
            let ua = window.navigator.userAgent;

            let msie = ua.indexOf('MSIE ');
            if (msie > 0) {
                // IE 10 or older => return 版本号
                return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
            }

            let trident = ua.indexOf('Trident/');
            if (trident > 0) {
                // IE 11 => return 版本号
                let rv = ua.indexOf('rv:');
                return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
            }

            let edge = ua.indexOf('Edge/');
            if (edge > 0) {
                // Edge (IE 12+) => return 版本号
                return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
            }

            // 其他浏览器
            return false;
        },
        /**
         * 生成一个指定前缀的 unique ID
         *
         * @param {string} prefix {string} 前缀
         * @returns {string}
         */
        getUniqueID: function (prefix) {
            return prefix + Math.floor(Math.random() * (new Date()).getTime());
        },

        /**
         * 获取在`breakpoints`中定义的响应式前缀对应的`width`
         *
         * @param mode {string} 响应式模式前缀 (e.g: xxl, xl, lg, md, sm)
         * @returns {number} width
         */
        getBreakpoint: function (mode) {
            return breakpoints[mode];
        },

        /**
         * 获取指定最小和最大范围内随机生成的整数值
         *
         * @param {number} min 最小
         * @param {number} max 最大
         * @returns {number}
         */
        getRandomInt: function (min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        },
        /**
         * css3 动画结束事件
         *
         * @param element {element} 目标元素
         * @param callback {function} 回调函数
         */
        transitionEnd: function (element, callback) {
            let transition;
            let transitions = {
                transition: 'transitionend',
                OTransition: 'oTransitionEnd',
                MozTransition: 'mozTransitionEnd',
                WebkitTransition: 'webkitTransitionEnd',
                msTransition: 'msTransitionEnd'
            };

            for (let t in transitions) {
                if (element.style[t] !== undefined) {
                    transition = transitions[t];
                }
            }

            EUtil.one(element, transition, callback);
        },
        /**
         * 初始化滚动条插件`PerfectScrollbar`
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         * @param options {object} 选项
         */
        scrollInit: function (element, options) {
            if (!element) return;
            let $element = $(element);

            // 初始化
            function init() {
                let ps;
                let height;
                // 如果高度是function
                if (options.height instanceof Function) {
                    height = parseInt(options.height.call());
                } else {
                    height = parseInt(options.height);
                }
                ps = EUtil.data($element).get('ps');
                // 如果设置mobileNativeScroll: true，当当前窗口模式为pad & mobile时，销毁滚动条
                if ((options.disableForMobile) && EUtil.isInResponsiveRange('tablet-and-mobile')) {
                    if (ps) {
                        if (options.resetHeightOnDestroy) {
                            // 销毁时高度设置为auto
                            $element.css('height', 'auto');
                        } else {
                            $element.css('overflow', 'auto');
                            if (height > 0) {
                                $element.css('height', height + 'px');
                            }
                        }
                        // 销毁滚动条
                        ps.destroy();
                        EUtil.data($element).remove('ps');
                    } else if (height > 0) {
                        $element.css('overflow', 'auto');
                        $element.css('height', height + 'px');
                    }
                    return;
                }

                if (height > 0) {
                    $element.css('height', height + 'px');
                }

                $element.css('overflow', 'hidden');

                if (ps) {
                    // 如果已经初始化，直接update
                    ps.update();
                } else {
                    $element.addClass('e-scroll');
                    ps = new PerfectScrollbar(element, {
                        wheelSpeed: 0.5,
                        swipeEasing: true,
                        wheelPropagation: false, // 当滚动到底时是否继续滚动父元素
                        minScrollbarLength: 40,
                        maxScrollbarLength: 300,
                        suppressScrollX: !$element.data('scroll-x') // 开启水平方向滚动条
                    });
                    EUtil.data($element).set('ps', ps);
                }
            }

            // 初始化
            init();
            // 如果开启了窗口拖动更新滚动条
            if (options.handleWindowResize) {
                $(window).on('resize', init);
            }
        },
        /**
         * 更新滚动条插件`PerfectScrollbar`
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        scrollUpdate: function (element) {
            let ps = EUtil.data(element).get('ps');
            if (ps) {
                ps.update();
            }
        },
        /**
         * 更新页面中所有滚动条插件`PerfectScrollbar`
         *
         * @param elements {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        scrollUpdateAll: function (elements) {
            $(elements).find('.ps').each(function () {
                EUtil.scrollUpdate(this);
            });
        },
        /**
         * 销毁滚动条插件`PerfectScrollbar`
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        scrollDestroy: function (element) {
            let ps = EUtil.data(element).get('ps');
            if (ps) {
                ps.destroy();
                EUtil.data(element).remove('ps');
            }
        },
        /**
         * 将运动条滚动到指定位置
         *
         * @param target {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         * @param top {number} 滚动到什么位置
         * @param speed {number} 动画时长，默认`500ms`
         */
        scrollTo: function (target, top, speed) {
            if (!target) {
                target = 'html, body';
            }
            if (!speed) {
                speed = 500;
            }
            $(target).animate({scrollTop: top}, speed);
        },
        /**
         * 判断对象是否为`undefined`或`null`，反方法isNotNull(obj)`，如果要判断字符串是否为空请用`isBlank(str)`
         *
         * @param obj {object} 待检查对象
         * @returns {boolean} true/false
         */
        isNull: function (obj) {
            return typeof obj === 'undefined' || obj === null;
        },
        /**
         * 判断对象是否不为`undefined`或`null`，如果要判断字符串是否为空请用`isNotBlank(str)`
         *
         * @param obj {object} 待检查对象
         * @returns {boolean} true/false
         */
        isNotNull: function (obj) {
            return !EUtil.isNull(obj);
        },
        /**
         * 判断字符串是否为`undefined`、`null`或`''`，反方法`isNotBlank(str)`
         *
         * @param str {string} 待检查字符串
         * @returns {boolean} true/false
         */
        isBlank: function (str) {
            return typeof str === 'undefined' || str === null || str === '';
        },
        /**
         * 判断字符串是否不为`undefined`、`null`或`''`
         *
         * @param str {string} 待检查字符串
         * @returns {boolean} true/false
         */
        isNotBlank: function (str) {
            return !EUtil.isBlank(str);
        },
        /**
         * 检查对象是否为Function类型
         *
         * @param fn {object} 待检查function
         * @returns {boolean} true/false
         */
        isFunction: function (fn) {
            return Object.prototype.toString.call(fn) === '[object Function]';
        },
        /**
         * 判断是否是数字，如果不是数字类型尝试转为数字类型，如果发生异常则返回`false`（方法不会抛出异常）
         *
         * @param obj {object} 待检查number
         * @returns {boolean} true/false
         */
        isNumber: function (obj) {
            // 是否为数字类型
            if (typeof obj === 'number') {
                return true;
            }
            // 如果不是，尝试转为数字类型，如果有异常，则返回false
            try {
                Number(obj);
                return true;
            } catch (e) {
                return false;
            }
        },
        /**
         * 检查对象是否是数组类型
         *
         * @param obj {object} 待检查对象
         * @returns {boolean} true/false
         */
        isArray: function (obj) {
            return obj && Array.isArray(obj);
        },
        /**
         * 判断是否为字符串类型
         *
         * @param obj {object} 待检查对象
         * @returns {boolean} true/false
         */
        isString: function (obj) {
            return typeof obj === 'string';
        },
        /**
         * 截取指定长度字符串，如果超出在后面拼接`...`
         *
         * @param str {string} 字符串
         * @param length {number} 长度
         * @returns {string} 截取后字符串
         */
        subStr: function (str, length) {
            if (str.length > length) {
                return str.substr(0, length) + "...";
            } else {
                return str;
            }
        },
        /**
         * 是否是顶级页面，根据最上层页面（window.top）是否是自己
         *
         * @return {boolean} true/false
         */
        isTopPage: function () {
            return window.top === window.self;
        },
        /**
         * 弹出toastr
         *
         * @param config {object} options
         */
        toastr: function (config) {
            if (EUtil.isTopPage()) {
                toastr[config.type](config.subtitle, config.title);
            } else {
                window.parent.EUtil.toastr(config);
            }
        },
        /**
         * 弹出提示（会查找顶级页面然后调用`SweetAlert2`中的`fire()`）
         *
         * @param config {object} option
         * @param callback {function} 回调
         */
        alert: function (config, callback) {
            if (EUtil.isTopPage()) {
                swal.fire(config).then(function (result) {
                    if (EUtil.isFunction(callback)) {
                        // todo: 检查`callback`是否正确调用
                        callback(result);
                    }
                });
            } else {
                window.parent.EUtil.alert(config, callback);
            }
        },
        /**
         * 弹出提示（级别: info-信息；会查找顶级页面然后调用`SweetAlert2`中的`fire()`）
         *
         * @param title {string} 标题
         * @param subtitle {string} 副标题
         */
        alertInfo: function (title, subtitle) {
            if (EUtil.isTopPage()) {
                swal.fire(title, subtitle, 'info');
            } else {
                window.parent.EUtil.alertInfo(title, subtitle);
            }
        },
        /**
         * 弹出提示（级别: success-成功；会查找顶级页面然后调用`SweetAlert2`中的`fire()`）
         *
         * @param title {string} 标题
         * @param subtitle {string} 副标题
         */
        alertSuccess: function (title, subtitle) {
            if (EUtil.isTopPage()) {
                swal.fire(title, subtitle, 'success');
            } else {
                window.parent.EUtil.alertSuccess(title, subtitle);
            }
        },
        /**
         * * 弹出提示（级别: warning-警告；会查找顶级页面然后调用`SweetAlert2`中的`fire()`）
         *
         * @param title {string} 标题
         * @param subtitle {string} 副标题
         */
        alertWarning: function (title, subtitle) {
            if (EUtil.isTopPage()) {
                swal.fire(title, subtitle, "warning");
            } else {
                window.parent.EUtil.alertWarning(title, subtitle);
            }
        },
        /**
         * 弹出提示（级别: error-错误；会查找顶级页面然后调用`SweetAlert2`中的`fire()`）
         *
         * @param title {string} 标题
         * @param subtitle {string} 副标题
         */
        alertError: function (title, subtitle) {
            if (EUtil.isTopPage()) {
                swal.fire(title, subtitle, "error");
            } else {
                window.parent.EUtil.alertError(title, subtitle);
            }
        },
        /**
         * 询问提示（会查找顶级页面然后调用`SweetAlert2`中的`fire()`）
         *
         * @param title {string} 标题
         * @param subtitle {string} 副标题
         * @param okCallback {function} 点击确定回调
         * @param cancelCallback {function} 点击取消回调
         */
        alertConfirm: function (title, subtitle, okCallback, cancelCallback) {
            if (EUtil.isTopPage()) {
                swal.fire({
                    title: title,
                    text: subtitle,
                    type: 'warning',
                    showCancelButton: true,
                    confirmButtonColor: '#DD6B55',
                    confirmButtonText: '确定',
                    cancelButtonText: '取消'
                }).then(function (obj) {
                    if (obj.value != null && obj.value) {
                        if (EUtil.isFunction(okCallback)) {
                            okCallback();
                        }
                    } else {
                        if (EUtil.isFunction(cancelCallback)) {
                            cancelCallback();
                        }
                    }
                })
            } else {
                window.parent.EUtil.alertConfirm(title, subtitle, okCallback, cancelCallback);
            }
        },
        /**
         * 显示等待遮罩
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        openWait: function (element) {
            EApp.block(element, {
                message: '请稍候...'
            });
        },
        /**
         * 移除等待遮罩
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        closeWait: function (element) {
            EApp.unblock(element);
        },
        /**
         * 设置按钮处理中状态，为了防止等待状态时按钮出现多个图标，如按钮有图标，会隐藏使用处理中图标代替；调用`offButtonWait()`后会恢复原有图标
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        setButtonWait: function (element) {
            EApp.progress(element, null);
        },
        /**
         * 取消按钮处理中状态
         *
         * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
         */
        offButtonWait: function (element) {
            EApp.unProgress(element);
        },
        /**
         * 封装jquery.ajax方法，提供了一些自定义属性以提高开发效率增加用户体验；默认使用`post`请求并且`dataType: 'json'`<br/>
         * 如果`dataType: 'json'`或响应信息为`object`类型，得到响应信息后尝试解析数据是否为成功并提示；如不需要上述处理逻辑可将`option.needAlert`设置为`false`<br/>
         * 得到后端响应后，取消`option.wait`等待状态，当HttpStatus不为200时，如`option.error`已存在则调用用户传入回调，如不存在则调用`EUtil.ajaxErrorCallback(XMLHttpRequest, textStatus, errorThrown)`
         *
         * @param option {object} 参数
         * @param option.wait {string} ajax执行中需要设置等待区域的选择器
         * @param option.needAlert {boolean} 服务器响应后是否弹出提示
         */
        ajax: function (option) {
            if (EUtil.isNotNull(option)) {
                let defaultOption = {
                    cache: false,
                    type: 'post',
                    dataType: 'json'
                };
                // 合并 option
                option = $.extend(true, {}, defaultOption, option);

                if (EUtil.isNotBlank(option.wait)) {
                    EUtil.openWait(option.wait);
                }
                $.ajax({
                    async: option.async,
                    url: option.url,
                    data: option.data,
                    contentType: option.contentType,
                    type: option.type,
                    dataType: option.dataType,
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        // 关闭等待
                        if (EUtil.isNotBlank(option.wait)) {
                            EUtil.closeWait(option.wait);
                        }
                        // 如传入error回调,则不调用公用ajaxErrorCallback
                        if (EUtil.isFunction(option.error)) {
                            // 用户自定义error，请处理EUtil.ajaxErrorCallback中提到的异常
                            option.error(XMLHttpRequest, textStatus, errorThrown);
                        } else {
                            EUtil.ajaxErrorCallback(XMLHttpRequest, textStatus, errorThrown);
                        }
                    },
                    success: function (res) {
                        // 若不成功禁止返回200
                        if (EUtil.isNotBlank(option.wait)) {
                            EUtil.closeWait(option.wait);
                        }
                        // 如果dataType未指定或者dataType为json，或者res是json对象
                        if ((EUtil.isBlank(option.dataType) || 'json' === option.dataType || typeof res === 'object') &&
                            EUtil.isNotBlank(res.code) && res.code.length === resultCode.length && option.needAlert) {
                            // 解析响应码
                            let result = ETool.analysisCode(res.code);
                            if (resultCode.code.success === result.code) {
                                // 如果成功，并且需要提示
                                EUtil.handleResult(res, null);
                            }
                        }
                        // 成功
                        if (EUtil.isFunction(option.success)) {
                            option.success(res);
                        }
                    }
                });
            } else {
                console.warn('EUtil.ajax方法不允许传入空参数');
            }
        },
        /**
         * 解析响应码并提示`message`，
         * 响应码规则请参考[响应码](/guide/standard.html#%E5%93%8D%E5%BA%94%E7%A0%81)
         *
         * @param res {object} 响应数据
         * @param callback {function|null} 回调
         */
        handleResult: function (res, callback) {
            /**
             * 弹出提示
             *
             * @param method {string} 方法 eg：successTip、infoTip、warnTip、errorTip
             * @param title {string} 标题
             * @param subtitle {string} 副标题，如果为空则不进行提示
             */
            let handleTip = function (method, title, subtitle) {
                if (EUtil.isNotBlank(subtitle)) {
                    ETool[method](title, subtitle);
                }
            };
            // 解析响应码
            let result = ETool.analysisCode(res.code);
            // 根据错误级别做不同处理
            switch (result.level) {
                case resultCode.level.success:
                    handleTip('successTip', commonTips.success, res.message);
                    break;
                case resultCode.level.warn:
                    // 警告。如果message不为空则提示，如果为空直接忽略
                    ETool.warnTip(commonTips.warn, res.message);
                    break;
                case resultCode.level.error:
                    // 错误。如果message不为空则提示，如果为空直接忽略
                    if (resultCode.modular.common === result.modular) {
                        // 公用模块
                        if (resultCode.code.badRequest === result.code) {
                            handleTip('errorTip', '参数错误', res.message);
                            break;
                        } else if (resultCode.code.unauthorized === result.code) {
                            // 会话失效 eg：登录失败、被踢出，在其他地方登录，管理员踢出
                            EUtil.alert({
                                title: '会话过期',
                                text: res.message,
                                type: 'warning',
                                confirmButtonText: '确定'
                            }, function () {
                                // 将顶级页面转到登录
                                window.top.location.href = basePath + '/login?' + res.data + '=1';
                            });
                            break;
                        } else if (resultCode.code.forbidden === result.code) {
                            // 无权访问
                            handleTip('errorTip', commonTips.forbidden, res.message);
                            // 权限可能被修改,刷新缓存用户数据
                            ETool.getUser(false);
                            break;
                        }
                    }
                    handleTip('errorTip', commonTips.fail, res.message);
                    break;
                default:
                    // 默认使用信息级别。如果message不为空则提示，如果为空直接忽略
                    handleTip('infoTip', commonTips.info, res.message);
                    break;
            }
            // 回调
            if (EUtil.isFunction(callback)) {
                callback(res);
            }
        },
        /**
         * 当后端响应`HttpStatus`为`500`时，解析错误类型并做相应处理
         *
         * @param XMLHttpRequest {object}
         * @param textStatus {object}
         * @param errorThrown {object}
         */
        ajaxErrorCallback: function (XMLHttpRequest, textStatus, errorThrown) {
            // 后端响应
            let res = XMLHttpRequest.responseJSON;
            if (typeof res === 'object' && res.code.length === resultCode.length) {
                // 如果响应码为6位，使用公用处理方法
                EUtil.handleResult(res, null);
            } else {
                if (httpStatus.badRequest === XMLHttpRequest.status) {
                    // 无效请求，一般为后端表单验证未通过
                    if (typeof res.errors !== 'undefined') {
                        let errors = [];
                        $(res.errors).each(function (index, error) {
                            errors.push(error.defaultMessage);
                        });
                        ETool.errorTip(commonTips.fail, errors.join('<br/>'));
                        return;
                    }
                }
                // 其他错误
                console.error(XMLHttpRequest.responseText);
                ETool.errorTip(XMLHttpRequest.status, res.message);
            }
        }
    }
}();

// 初始化
$(document).ready(function () {
    EUtil.init();
});
"use strict";

/**
 * 提供表单元素初始化、加载提示、动画等公用方法
 */
let EApp = function () {
    // 状态色
    let colors = {};
    /**
     * 全局动画设置
     *
     * @type {{in: string, out: string}}
     */
    const animate = {
        // 出现动画
        in: 'fadeIn',
        // 消失动画
        out: 'fadeOut'
    };

    /**
     * 初始化 tooltips
     *
     * @param $element {object} 元素
     */
    let initTooltip = function ($element) {
        let skin = $element.data('skin') ? 'tooltip-' + $element.data('skin') : '';
        let width = $element.data('width') === 'auto' ? 'tooltop-auto-width' : '';
        let triggerValue = $element.data('trigger') ? $element.data('trigger') : 'hover';

        $element.tooltip({
            trigger: triggerValue,
            template: '<div class="tooltip ' + skin + ' ' + width + '" role="tooltip">\
                <div class="arrow"></div>\
                <div class="tooltip-inner"></div>\
            </div>'
        });
    };

    /**
     * 初始化 tooltips，将会自动初始化`[data-toggle="e-tooltip"]`和`.e-tooltip-auto`的元素
     */
    let initTooltips = function () {
        $('[data-toggle="e-tooltip"], .e-tooltip-auto').each(function () {
            initTooltip($(this));
        });
    };

    /**
     * 初始化 popover
     *
     * @param $element {object} 元素
     */
    let initPopover = function ($element) {
        let skin = $element.data('skin') ? 'popover-' + $element.data('skin') : '';
        let triggerValue = $element.data('trigger') ? $element.data('trigger') : 'hover';

        $element.popover({
            trigger: triggerValue,
            template: '\
            <div class="popover ' + skin + '" role="tooltip">\
                <div class="arrow"></div>\
                <h3 class="popover-header"></h3>\
                <div class="popover-body"></div>\
            </div>'
        });
    };

    /**
     * 初始化 popover，将会自动初始化`[data-toggle="e-popover"]`和`.e-popover-auto`的元素
     */
    let initPopovers = function () {
        $('[data-toggle="e-popover"], .e-popover-auto').each(function () {
            initPopover($(this));
        });
    };

    /**
     * 初始化滚动条插件`PerfectScrollbar`
     */
    let initScroll = function () {
        $('[data-scroll="true"], .e-scrollable').each(function () {
            let $element = $(this);
            EUtil.scrollInit(this, {
                mobileNativeScroll: true,
                handleWindowResize: true,
                height: function () {
                    if (EUtil.isInResponsiveRange('tablet-and-mobile') && $element.data('mobile-height')) {
                        return $element.data('mobile-height');
                    } else {
                        return $element.data('height');
                    }
                }
            });
        });
    };

    /**
     * 初始化 Card
     *
     * @param element {element|string} 目标元素，支持`选择器`、`Element`、`jQuery对象`
     * @param options {object} 选项
     */
    let initCard = function (element, options) {
        new ECard(element, options);
    };

    /**
     * 初始化 Card，将会默认初始化`[data-card="true"]`的元素
     */
    let initCards = function () {
        $('[data-card="true"]').each(function () {
            let $el = $(this);
            if ($el.data('data-card-initialized') !== true) {
                initCard(this, {});
                $el.data('data-card-initialized', true);
            }
        });
    };

    /**
     * 初始化 Alert 的关闭按钮，点击`[data-close=alert]`将隐藏Alert
     */
    let initAlerts = function () {
        $('body').on('click', '[data-close=alert]', function () {
            $(this).closest('.alert').hide();
        });
    };

    /**
     * 处理插件（select、input）值改变时执行表单验证
     *
     * @param $element {object} jQuery元素
     */
    let handleValidate = function ($element) {
        let $form = $element.parents('.e-form');
        let validate = EUtil.data($form).get('validate');
        if (validate) {
            validate.element($element);
        }
    };

    /**
     * 初始化bootstrap select
     * @param selector {string} 选择器
     */
    let initSelectPicker = function (selector) {
        let _initSelectPicker = function ($element) {
            let placeholder = $element.data('placeholder');
            $element.selectpicker({
                noneSelectedText: EUtil.isNotBlank(placeholder) ? placeholder : ''
            }).on('changed.bs.select', function () {
                handleValidate($element);
            });
        };
        $(selector).each(function () {
            let $element = $(this);
            if (!$element.data('select-initialized')) {
                $element.attr('data-select-initialized', 'true');

                // 指定了字典类型
                if (EUtil.isNotBlank($element.data('dict-type'))) {
                    if ($element.data('no-empty') !== true) {
                        $element.append('<option value=""></option>');
                    }
                    initDictSelect($element);
                }
                // 指定了 data-url 属性,ajax请求接口获取下拉菜单
                if (EUtil.isNotBlank($element.data('url'))) {
                    if ($element.data('no-empty') !== true) {
                        $element.append('<option value=""></option>');
                    }
                    initSelectByUrl($element, function ($element) {
                        _initSelectPicker($element);
                    });
                } else {
                    let defaultValue = $element.data('value');
                    if(EUtil.isNotBlank(defaultValue)){
                        if ('multiple' === $element.attr('multiple')) {
                            if(!EUtil.isString(defaultValue)){
                                defaultValue = defaultValue + '';
                            }
                            $element.val(defaultValue.split(','));
                        } else {
                            $element.val(defaultValue);
                        }
                    }
                    _initSelectPicker($element);
                }
            }
        });
    };

    /**
     * 初始化select2
     *
     * @param selector {string} 选择器
     */
    let initSelect2Picker = function (selector) {
        let _initSelectPicker = function ($element) {
            $element.select2({
                placeholder: $element.attr('title'),
                // 多选时最多可以选择几个
                maximumSelectionLength: $element.data('max-options'),
                // 是否显示搜索框
                minimumResultsForSearch: $element.data('live-search') ? 0 : Infinity,
                tags: $element.data('tags') // 是否可添加标签
            });
        };

        $(selector).each(function () {
            let $element = $(this);
            if (!$element.data('select-initialized')) {
                $element.attr('data-select-initialized', 'true');

                // 指定了字典类型
                if (EUtil.isNotBlank($element.data('dict-type'))) {
                    if ($element.data('no-empty') !== true) {
                        $element.append('<option value=""></option>');
                    }
                    initDictSelect($element);
                }
                // 指定了 data-url 属性,ajax请求接口获取下拉菜单
                if (EUtil.isNotBlank($element.data('url'))) {
                    if ($element.data('no-empty') !== true) {
                        $element.append('<option value=""></option>');
                    }
                    initSelectByUrl($element, function ($element) {
                        _initSelectPicker($element);
                    });
                } else {
                    if (EUtil.isNotBlank($element.data('value'))) {
                        $element.val($element.data('value'));
                    }
                    _initSelectPicker($element);
                }
            }
        });
    };
    /**
     * 初始化input-mask
     *
     * @param selector {string} 选择器
     */
    let initInputMask = function (selector) {
        $(selector).each(function () {
            let $element = $(this);
            if (!$element.data('select-initialized')) {
                $element.attr('data-select-initialized', 'true');
                $element.inputmask({
                    mask: $element.data('mask'),
                    placeholder: $element.data('placeholder')
                });
            }
        });
    };
    /**
     * 初始化auto-size
     *
     * @param selector {string} 选择器
     */
    let initAutoSize = function (selector) {
        $(selector).each(function () {
            let $element = $(this);
            if (!$element.data('select-initialized')) {
                $element.attr('data-select-initialized', 'true');
                autosize($element);
            }
        });
    };
    /**
     * 初始化Clipboard
     *
     * @param selector {string} 选择器
     */
    let initClipboard = function (selector) {
        new ClipboardJS(selector).on('success', function (e) {
            e.clearSelection();
        });
    };
    /**
     * 初始化 Form Validate
     *
     * @param selector {string} 选择器
     */
    let initFormValidate = function (selector) {
        $(selector).each(function () {
            let $element = $(this);
            let validate;
            if (!EUtil.data($element).has('validate')) {
                // 初始化并放到全局缓存，bootstrap-select和select2会用到
                validate = $element.validate({
                    invalidHandler: function (event, validator) {
                        if (validator.errorList.length) {
                            // 有错误，滚动到错误处
                            EUtil.scrollTo(null, Math.max($(validator.errorList[0].element).offset().top - 25, 0));
                        }
                    }
                });
                EUtil.data($element).set('validate', validate);
            }
        });
    };

    /**
     * 初始化checkbox字典
     *
     * @param selector {string} jquery选择器
     */
    let initCheckbox = function (selector) {
        initCheckBoxOrRadio($(selector), 'checkbox');
    };
    /**
     * 初始化radio字典
     *
     * @param selector {string} jquery选择器
     */
    let initRadio = function (selector) {
        initCheckBoxOrRadio($(selector), 'radio');
    };
    /**
     * 初始化checkbox/radio字典
     *
     * @param $elements {object} jquery元素
     * @param type {string} 类型
     */
    let initCheckBoxOrRadio = function ($elements, type) {
        let defaultChecked = function (value, code) {
            if (EUtil.isNumber(value)) {
                try {
                    code = Number(code);
                } catch (e) {
                }
                if (value === code) {
                    return 'checked';
                }
            } else {
                if (EUtil.isNotBlank(value)) {
                    let values = value.split(',');
                    if (values.indexOf(code) > -1) {
                        return 'checked';
                    }
                }
            }
            return '';
        };
        if ($elements != null && $elements.length > 0) {
            $($elements).each(function (index, element) {
                let $element = $(element);
                // 检查是否初始化过
                if (!$element.data(type + '-initialized')) {
                    $element.attr('data-' + type + '-initialized', 'true');
                    // 字典类型
                    let dictType = $element.data('dict-type');
                    if (EUtil.isNotBlank(dictType)) {
                        // 方向
                        let direction = $element.data('direction');
                        let containerClass = 'e-' + type + '-inline';
                        if ('vertical' === direction) {
                            containerClass = 'e-' + type + '-list';
                        }
                        let dicts = ETool.getSysDictArrayByDictType(dictType);
                        if (dicts != null && dicts.length > 0) {
                            let value = $element.data('value');
                            let name = $element.data('name');
                            let required = $element.data('required');
                            let html = '<div class="' + containerClass + '">';
                            $(dicts).each(function (index, dict) {
                                html += '<label class="e-' + type + '">\
                                        <input name="' + name + '" value="' + dict.code + '" type="' + type + '" ' +
                                    defaultChecked(value, dict.code) + ' ' + (required ? 'required' : '') + '> ' + dict.name + '\
                                        <span></span>\
                                    </label>';
                            });
                            html += '</div>';
                            $element.html(html);
                        }
                    }
                }
            });
        }
    };
    /**
     * 初始化日期插件
     *
     * @param selector {string} 选择器
     */
    let initDateTimePicker = function (selector) {
        /**
         * 获取默认视图
         * 0-分 1-时 2-日 3-月 4-年
         * @param format {string} 日期格式
         * @return {number}
         */
        function getStartView(format) {
            if (format === 'yyyy') {
                return 4;
            } else if (format.length === 'yyyy-mm'.length) {
                return 3;
            } else if (format.length === 'hh:ii'.length || format.length === 'hh:ii:ss'.length) {
                return 1;
            } else {
                return 2;
            }
        }

        /**
         * 获取最小视图
         * 0-分 1-时 2-日 3-月 4-年
         * @param format {string} 日期格式
         * @return {number}
         */
        function getMinView(format) {
            if (format === 'yyyy') {
                return 4;
            } else if (format.length === 'yyyy-mm'.length) {
                return 3;
            } else if (format.length === 'yyyy-mm-dd'.length) {
                return 2;
            } else if (format.length === 'yyyy-mm-dd hh'.length || format === 'yyyy-mm-dd hh:00') {
                return 1;
            } else {
                return 0;
            }
        }

        // default
        let defaultOptions = {
            language: 'zh-CN',
            todayHighlight: true, // 高亮今日
            autoclose: true, // 自动关闭
            todayBtn: false, // 显示今日按钮
            format: 'yyyy-mm-dd',
            pickerPosition: 'bottom-right'
        };
        $(selector).each(function () {
            let $element = $(this);
            let options = {
                pickerPosition: $element.data('position'),
                format: $element.data('format'),
                todayBtn: $element.data('show-today'),
                startView: $element.data('start-view'),
                minView: $element.data('min-view')
            };
            options = $.extend(true, {}, defaultOptions, options);
            if (typeof options.startView === 'undefined') {
                options.startView = getStartView(options.format);
            }
            if (typeof options.minView === 'undefined') {
                options.minView = getMinView(options.format);
            }
            $element.datetimepicker(options).change(function () {
                handleValidate($element);
            });
        });
    };
    /**
     * 初始化日期范围
     *
     * @param selector {string} 选择器
     */
    let initDateRange = function (selector) {
        $(selector).each(function () {
            // 查找div内的input，必须为2个，如果不是2个则忽略不进行初始化
            let $inputs = $(this).find('input[type="text"]');
            if ($inputs.length === 2) {
                // 插件已经在initTimePicker中初始化，所以这里只需在changeDate event时更改startDate/endDate
                // 请在日期插件初始化后调用initDateRange
                let $strDate = $($inputs[0]),
                    $endDate = $($inputs[1]);
                $strDate.on('changeDate', function (ev) {
                    let strDate = ev.date;
                    if (EUtil.isNotBlank(strDate)) {
                        $endDate.datetimepicker('setStartDate', strDate);
                    } else {
                        $endDate.datetimepicker('setStartDate', null);
                    }
                });

                $endDate.on('changeDate', function (ev) {
                    let endDate = ev.date;
                    if (EUtil.isNotBlank(endDate)) {
                        $strDate.datetimepicker('setEndDate', endDate);
                    } else {
                        $strDate.datetimepicker('setEndDate', null);
                    }
                });
            }
        });
    };
    /**
     * 初始化日期范围插件
     *
     * @param selector {string} 选择器
     */
    let initDateRangePicker = function (selector) {
        // default
        let defaultOptions = {
            buttonClasses: 'btn',
            applyClass: 'btn-brand',
            cancelClass: 'btn-secondary',
            autoUpdateInput: false, // 不自动填充内容
            locale: {
                cancelLabel: '取消',
                applyLabel: '确定',
                format: 'YYYY-MM-DD'
            },
            timePicker: false, // 是否显示时间
            timePickerIncrement: 10 // 增量
        };
        $(selector).each(function () {
            if (typeof $.fn.daterangepicker === 'undefined' && EDebug) {
                console.warn('EApp.initDateRangePicker缺少Bootstrap Date Range Picker');
                return;
            }
            let $element = $(this);
            let options = {
                timePicker: $element.data('time'),
                timePickerIncrement: $element.data('time-increment'),
                startDate: $element.data('start-date'),
                endDate: $element.data('end-date'),
                locale: {
                    format: $element.data('format')
                }
            };
            options = $.extend(true, {}, defaultOptions, options);
            // 默认值，date-*-date优先级高于value
            if (EUtil.isBlank(options.startDate) && EUtil.isBlank(options.endDate) && EUtil.isNotBlank($element.val())) {
                let dates = $element.val().split('~');
                if (dates.length !== 2) {
                    return;
                }
                options.startDate = $.trim(dates[0]);
                options.endDate = $.trim(dates[1]);
            }
            // 将字符串日期格式转为Date类型，依赖于moment.js
            if (EUtil.isNotBlank(options.startDate) && EUtil.isNotBlank(options.endDate)) {
                try {
                    options.startDate = moment(options.startDate, options.locale.format);
                    options.endDate = moment(options.endDate, options.locale.format);
                    $element.val(options.startDate.format(options.locale.format) + ' ~ ' + options.endDate.format(options.locale.format));
                } catch (e) {
                    console.error(e);
                    return;
                }
            }

            $element.daterangepicker(options, function (start, end, label) {
                $element.val(start.format(options.locale.format) + ' ~ ' + end.format(options.locale.format));
                handleValidate($element);
            });
        });
    };

    /**
     * 初始化时间插件
     *
     * @param selector {string} 选择器
     */
    let initTimePicker = function (selector) {
        // default
        let defaultOptions = {
            minuteStep: 1, // 增/减量
            showSeconds: false, // 显示秒
            showMeridian: false // 是否使用12小时模式
        };
        $(selector).each(function () {
            if (typeof $.fn.timepicker === 'undefined' && EDebug) {
                console.warn('EApp.initTimePicker缺少Bootstrap Time Picker');
                return;
            }
            let $element = $(this);
            let options = {
                defaultTime: $element.val(),
                showSeconds: $element.data('show-seconds'), // 是否显示单位-秒
                showMeridian: $element.data('show-meridian') // true 12hr mode、false 24hr mode
            };
            options = $.extend(true, {}, defaultOptions, options);
            $element.timepicker(options);
        });
    };
    /**
     * 初始化TouchSpin插件
     *
     * @param selector {string} 选择器
     */
    let initTouchSpin = function (selector) {
        // default
        let defaultOptions = {
            buttondown_class: 'btn btn-secondary',
            buttonup_class: 'btn btn-secondary',
            verticalup: '<i class="la la-plus"></i>',
            verticaldown: '<i class="la la-minus"></i>'
        };
        $(selector).each(function () {
            if (typeof $.fn.TouchSpin === 'undefined' && EDebug) {
                console.warn('EApp.initTouchSpin缺少Bootstrap TouchSpin');
                return;
            }
            let $element = $(this);
            let options = {
                prefix: $element.data('prefix'), // 前缀
                postfix: $element.data('postfix'), // 后缀
                min: $element.data('min'),
                max: $element.data('max'),
                step: $element.data('step'), // 递增/减量
                decimals: $element.data('decimals'), // 小数位数
                boostat: $element.data('boostat'), // 第n步后加速
                maxboostedstep: $element.data('maxboostedstep'), // 加速最大倍数
                verticalbuttons: $element.data('vertical')
            };
            options = $.extend(true, {}, defaultOptions, options);
            $element.TouchSpin(options);
        });
    };
    /**
     * 初始化MaxLength插件
     *
     * @param selector {string} 选择器
     */
    let initMaxLength = function (selector) {
        $(selector).each(function () {
            let $element = $(this);
            let options = {
                separator: ' 个字符，可输入 ',
                preText: '已输入 ',
                postText: ' 个字符',
                validate: true,
                warningClass: 'badge badge-brand badge--inline',
                limitReachedClass: 'badge badge-brand badge--inline'
            };
            $element.maxlength(options);
        });
    };

    /**
     * 根据字典类型加载select > option
     *
     * @param $element {object} select对象
     */
    let initDictSelect = function ($element) {
        let dictType = $element.data('dict-type');
        let dicts = ETool.getSysDictArrayByDictType(dictType);
        if (dicts != null && dicts.length > 0) {
            // 倒序排列？
            let isReverse = $element.data('reverse');
            if(isReverse){
                dicts = dicts.reverse();
            }
            $(dicts).each(function (index, dict) {
                $element.append('<option value="' + dict.code + '">' + dict.name + '</option>');
            });
        }
    };
    /**
     * 根据 data-url 加载select > option
     *
     * @param $element {object} 元素
     * @param callback {function} 回调
     */
    let initSelectByUrl = function ($element, callback) {
        EUtil.ajax({
            url: $element.data('url'),
            fail: function (res) {
                console.error(res.message);
                if (EUtil.isFunction(callback)) {
                    callback($element);
                }
            },
            success: function (res) {
                if (typeof res.data !== 'undefined' && res.data.length > 0) {
                    $(res.data).each(function (index, obj) {
                        $element.append('<option value="' + obj.value + '">' + obj.text + '</option>');
                    });
                }
                if (EUtil.isNotBlank($element.data('value'))) {
                    $element.val($element.data('value'));
                }
                if (EUtil.isFunction(callback)) {
                    callback($element);
                }
            }
        });
    };
    /**
     * 合并默认Dropzone参数
     *
     * @param options {object} 参数
     * @return {object} 合并后参数
     */
    let extendDropzoneDefaultOption = function (options) {
        let _defaultOptions = {
            url: basePath + '/auth/upload',
            paramName: "file",
            maxFiles: 1,
            maxFilesize: 10, // MB
            addRemoveLinks: true,
            autoQueue: true,
            dictDefaultMessage: Dropzone.dictDefaultMessage,
            dictFallbackMessage: Dropzone.dictFallbackMessage,
            dictFallbackText: Dropzone.dictFallbackText,
            dictFileTooBig: Dropzone.dictFileTooBig,
            dictInvalidFileType: Dropzone.dictInvalidFileType,
            dictResponseError: Dropzone.dictResponseError,
            dictCancelUpload: Dropzone.dictCancelUpload,
            dictUploadCanceled: Dropzone.dictUploadCanceled,
            dictCancelUploadConfirmation: Dropzone.dictCancelUploadConfirmation,
            dictRemoveFile: Dropzone.dictRemoveFile,
            dictMaxFilesExceeded: Dropzone.dictMaxFilesExceeded
        };
        return $.extend(true, {}, _defaultOptions, options);
    };

    return {
        init: function () {
            // 移除页面加载中class
            EApp.removePageLoading();
            // 初始化插件
            EApp.initComponents();
        },
        /**
         * 初始化插件
         */
        initComponents: function () {
            // 滚动条
            initScroll();
            // 卡片
            initCards();
            // 文字提示
            initTooltips('.e-tooltip, [data-toggle="e-tooltip"]');
            // 气泡提示
            initPopovers('.e-popover, [data-toggle="e-popover"]');
            // 提示（关闭）
            initAlerts();
            // Bootstrap Select 下拉
            initSelectPicker('.select-picker');
            // Select2 下拉
            initSelect2Picker('.select2-picker');
            // 字典checkbox
            initCheckbox('.checkbox-dict');
            // 字典radio
            initRadio('.radio-dict');
            // 日期时间
            initDateTimePicker('.date-picker');
            // 起始日期 基于上面插件，请勿改变调用顺序
            initDateRangePicker('.date-range-picker');
            // 时间插件
            initTimePicker('.time-picker');
            // 日期范围
            initDateRange('.input-date-range');
            // TouchSpin
            initTouchSpin('.touch-spin');
            // 最大长度
            initMaxLength('.max-length');
            // 输入格式
            initInputMask('.input-mask');
            // 自动大小
            initAutoSize('.auto-size');
            // 剪切板
            initClipboard('.clipboard');
            // 表单验证
            initFormValidate('.e-form');
        },
        /**
         * 初始化radio
         */
        initRadio: function () {
            initRadio('.radio-dict');
        },
        /**
         * 初始化checkbox
         */
        initCheckbox: function () {
            initCheckbox('.checkbox-dict');
        },
        /**
         * 初始化tooltips
         */
        initTooltips: function () {
            initTooltips();
        },
        /**
         * 初始化tooltip
         *
         * @param el {object} 要初始化的元素
         */
        initTooltip: function (el) {
            initTooltip(el);
        },
        /**
         * 初始化popovers
         */
        initPopovers: function () {
            initPopovers();
        },
        /**
         * 初始化popover
         *
         * @param el {object} 要初始化的元素
         */
        initPopover: function (el) {
            initPopover(el);
        },
        /**
         * 初始化bootstrap select
         *
         * @param selector {string} 选择器
         */
        initSelectPicker: function (selector) {
            return initSelectPicker(selector);
        },
        /**
         * 初始化select2
         *
         * @param selector {string} 选择器
         */
        initSelect2Picker: function (selector) {
            initSelect2Picker(selector);
        },
        /**
         * 初始化input-mask
         *
         * @param selector {string} 选择器
         */
        initInputMask: function (selector) {
            initInputMask(selector);
        },
        /**
         * 初始化日期插件
         *
         * @param selector {string} 选择器
         */
        initDateTimePicker: function (selector) {
            initDateTimePicker(selector);
        },
        /**
         * 初始化日期范围插件
         *
         * @param selector {string} 选择器
         */
        initDateRangePicker: function (selector) {
            initDateRangePicker(selector);
        },
        /**
         * 初始化日期周期
         *
         * @param selector {string} 选择器
         */
        initDateRange: function (selector) {
            initDateRange(selector);
        },
        /**
         * 初始化时间插件
         *
         * @param selector {string} 选择器
         */
        initTimePicker: function (selector) {
            initTimePicker(selector);
        },
        /**
         * 初始化文件上传
         *
         * @param options {object} 参数
         * @returns {object} Dropzone
         */
        initDropzone: function (options) {
            options = extendDropzoneDefaultOption(options);
            if (EUtil.isFunction(options.success)) {
                options._success = options.success;
            }
            options.success = function (file) {
                if (options._success) {
                    options._success($.parseJSON(file.xhr.response), file);
                }
                if (file.previewElement) {
                    return file.previewElement.classList.add("dz-success");
                }
            }
            if (EUtil.isFunction(options.complete)) {
                options._complete = options.complete;
            }

            options.complete = function (file) {
                if (options._complete) {
                    options._complete($.parseJSON(file.xhr.response), file);
                }
                if (file._removeLink) {
                    file._removeLink.innerHTML = this.options.dictRemoveFile;
                }
                if (file.previewElement) {
                    return file.previewElement.classList.add("dz-complete");
                }
            }
            return new Dropzone(options.selector, options);
        },

        /**
         * 初始化文件上传
         *
         * @param options {object} 参数
         * @returns {object} Dropzone
         */
        initMiniDropzone: function (options) {
            options = extendDropzoneDefaultOption(options);
            if (typeof options.autoQueue === 'undefined') {
                options.autoQueue = false;
            }
            // 不显示默认的移除按钮
            options.addRemoveLinks = false;
            // 上传列队放置位置
            options.previewsContainer = options.selector + ' .dropzone-items';
            // 选择文件按钮
            options.clickable = options.selector + ' .dropzone-select';
            // 文件列队模板
            options.previewTemplate = '\
            <div class="dropzone-item">\
                <div class="dropzone-file">\
                    <div class="dropzone-filename"><span data-dz-name>文件名称</span> <strong>(<span  data-dz-size>大小</span>)</strong></div>\
                    <div class="dropzone-error" data-dz-errormessage></div>\
                </div>\
                <div class="dropzone-progress">\
                    <div class="progress">\
                        <div class="progress-bar e-bg-brand" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" data-dz-uploadprogress></div>\
                    </div>\
                </div>\
                <div class="dropzone-toolbar">\
                    <span class="dropzone-start"><i class="flaticon2-arrow"></i></span>\
                    <span class="dropzone-cancel" data-dz-remove style="display: none;"><i class="flaticon2-cross"></i></span>\
                    <span class="dropzone-delete" data-dz-remove><i class="flaticon2-cross"></i></span>\
                </div>\
            </div>';

            if (EUtil.isFunction(options.success)) {
                options._success = options.success;
                options.success = function (file) {
                    options._success($.parseJSON(file.xhr.response), file);
                }
            }
            if (EUtil.isFunction(options.complete)) {
                options._complete = options.complete;
                options.complete = function (file) {
                    options._complete($.parseJSON(file.xhr.response), file);
                }
            }
            let dropzone = new Dropzone(options.selector, options);

            // 选择文件
            dropzone.on('addedfile', function (file) {
                // 开始上传
                file.previewElement.querySelector(options.selector + ' .dropzone-start').onclick = function () {
                    dropzone.enqueueFile(file);
                };
                // 显示全部上传和全部移除按钮
                $(options.selector + ' .dropzone-upload, ' + options.selector + ' .dropzone-remove-all').css('display', 'inline-block');
            });

            // 每个文件发送之前调用
            dropzone.on('sending', function (file) {
                // 显示进度条
                $(options.selector + ' .progress-bar').css('opacity', '1');
                // 禁用开始上传按钮
                file.previewElement.querySelector(options.selector + ' .dropzone-start').setAttribute('disabled', 'disabled');
            });

            // 上传成功或错误时调用
            dropzone.on('complete', function (file) {
                // 隐藏进度、上传按钮
                let thisProgressBar = options.selector + ' .dz-complete';
                setTimeout(function () {
                    $(thisProgressBar + ' .progress-bar, ' + thisProgressBar + ' .progress, ' + thisProgressBar + ' .dropzone-start').css('opacity', '0');
                }, 300);
                if ('success' === file.status) {
                    setTimeout(function () {
                        // 防止短时间内多个文件同时上传导致一次性全部消失无动画，一个一个删除
                        let $animateElement = $(thisProgressBar + ':not(.dz-error):not(.animated):eq(0)').addClass('animated');
                        if ($animateElement.length) {
                            EApp.animateCSS($animateElement[0], 'fadeOut', function () {
                                console.log('end');
                                $animateElement.remove();
                            });
                        }
                    }, 500);
                }
            });

            // 上传全部
            $(options.selector + ' .dropzone-upload').click(function () {
                dropzone.enqueueFiles(dropzone.getFilesWithStatus(Dropzone.ADDED));
            });

            // 移除全部
            $(options.selector + ' .dropzone-remove-all').click(function () {
                $(options.selector + ' .dropzone-upload, ' + options.selector + ' .dropzone-remove-all').css('display', 'none');
                dropzone.removeAllFiles(true);
            });

            // 队列中的所有文件完成上传
            dropzone.on('queuecomplete', function () {
                $(options.selector + ' .dropzone-upload').css('display', 'none');
            });

            // 从列表中删除文件
            dropzone.on('removedfile', function (file) {
                if (dropzone.files.length < 1) {
                    $(options.selector + ' .dropzone-upload, ' + options.selector + ' .dropzone-remove-all').css('display', 'none');
                }
            });
            return dropzone;
        },

        /**
         * 显示加载提示
         *
         * @param target {string} 选择器
         * @param options {object} 选项
         */
        block: function (target, options) {
            let el = $(target);

            options = $.extend(true, {
                opacity: 0.5,
                overlayColor: '#fff',
                type: 'grow',
                state: 'success',
                size: 'sm',
                centerX: true,
                centerY: true,
                message: '',
                shadow: true,
                width: 'auto'
            }, options);

            let html;

            let type = options.type ? ' spinner-' + options.type : 'spinner-grow';
            let state = options.state ? ' e-font-' + options.state : 'e-font-brand';
            let size = options.size ? ' spinner-' + options.size : 'spinner-sm';

            let spinner = '<div class="' + type + size + state + '">\
                  <span class="sr-only">Loading...</span>\
                </div>';

            if (options.message && options.message.length > 0) {
                let classes = 'block-ui ' + (options.shadow === false ? 'block-ui-no-shadow' : '') + (options.size ? ' block-ui-' + options.size : '');
                spinner = '<span class="' + type + size + state + '"></span><span class="spinner-message">' + options.message + '</span>';
                html = '<div class="' + classes + '">' + spinner + '</div>';

                // 计算宽度
                let $temporary = $('<div/>').addClass(classes).html(spinner).css({
                    position: 'absolute',
                    visibility: 'hidden',
                    display: 'table'
                });
                $('body').append($temporary);
                options.width = $temporary.outerWidth() + 10;
                $temporary.remove();

                if (target === 'body') {
                    html = '<div class="' + classes + '" style="margin-left:-' + (options.width / 2) + 'px;">' + spinner + '</div>';
                }
            } else {
                html = spinner;
            }

            let params = {
                message: html,
                centerY: options.centerY,
                centerX: options.centerX,
                css: {
                    top: '30%',
                    left: '50%',
                    border: '0',
                    padding: '0',
                    backgroundColor: 'none',
                    width: options.width
                },
                overlayCSS: {
                    backgroundColor: options.overlayColor,
                    opacity: options.opacity,
                    cursor: 'wait',
                    zIndex: '10'
                },
                onUnblock: function () {
                    if (el && el[0]) {
                        el.css('position', '');
                        el.css('zoom', '');
                    }
                }
            };

            if (target === 'body') {
                params.css.top = '50%';
                $.blockUI(params);
            } else {
                let el = $(target);
                el.block(params);
            }
        },
        /**
         * 隐藏加载提示
         *
         * @param target {string} 选择器
         */
        unblock: function (target) {
            if (target && target !== 'body') {
                $(target).unblock();
            } else {
                $.unblockUI();
            }
        },
        /**
         * 显示页面级别加载提示
         *
         * @param options {object}
         */
        blockPage: function (options) {
            if (options == null) {
                options = {
                    message: '页面加载中，请稍候...'
                }
            }
            EApp.block('body', options);
        },
        /**
         * 隐藏页面级别加载提示
         */
        unblockPage: function () {
            return EApp.unblock('body');
        },
        /**
         * 在按钮上面显示加载中图标
         *
         * @param target {string|element} 选择器
         * @param options {object} 选项
         */
        progress: function (target, options) {
            let $btns = $(target);
            if ($btns.length) {
                $btns.each(function () {
                    let $element = $(this);
                    let type = (options && options.type) ? options.type : 'spinner-grow';
                    let color = (options && options.color) ? options.color : '';
                    let size = (options && options.size) ? 'spinner-' + options.size : 'spinner-sm';
                    let classes = 'e-spinner ' + type + ' ' + color + '' + size;
                    EApp.unProgress($element);
                    $element.attr('disabled', 'disabled').addClass('btn--spinner').html('<span class="' + classes + '" role="status" aria-hidden="true"></span>' + $element.html());
                });
            }
        },
        /**
         * 隐藏按钮上面显示加载中图标
         * @param target {string} 选择器
         */
        unProgress: function (target) {
            $(target).removeAttr('disabled').removeClass('btn--spinner').find('.e-spinner').remove();
        },
        /**
         * 根据状态名称获取颜色
         *
         * @param name {string} 状态名称
         * @return {string} 颜色
         */
        getStateColor: function (name) {
            return colors["state"][name];
        },
        /**
         * 获取base color
         *
         * @param type {string} 类型
         * @param level {number} 级别
         * @return {string} 颜色
         */
        getBaseColor: function (type, level) {
            return colors["base"][type][level - 1];
        },
        /**
         * 初始化标签页
         */
        initTabs: function () {
            window.tabPage = new ETabPage();
        },
        /**
         * 打开页面
         * @param name {string} 页面名称
         * @param url {string} 访问地址
         * @param canClose {boolean} 是否可以关闭
         * @param sourcePagePathname {string} 来源页面pathname，用于关闭标签页后返回来源页面
         */
        openPage: function (name, url, canClose, sourcePagePathname) {
            // 来源页面pathname
            if(EUtil.isBlank(sourcePagePathname)){
                sourcePagePathname = window.location.pathname;
            }
            if (top.location !== self.location && typeof tabPage === 'undefined') { // 有父页面,并且 tabPage is null
                parent.EApp.openPage(name, url, canClose, sourcePagePathname);
            } else {
                if (typeof tabPage !== 'undefined') {
                    tabPage.addTab(name, url, canClose, sourcePagePathname);
                } else {
                    window.open(url);
                }
            }
        },
        /**
         * 关闭当前页面
         */
        closeCurrentPage: function () {
            EApp.closeTabByUrl(window.location.pathname);
        },
        /**
         * 关闭指定url页面
         *
         * @param url {string} 页面url
         */
        closeTabByUrl: function (url) {
            if (top.location !== self.location && typeof tabPage === 'undefined') {
                // 有父页面,并且 tabPage is null
                parent.EApp.closeTabByUrl(url);
            } else {
                if (typeof tabPage !== 'undefined') {
                    tabPage.closeTabByUrl(url);
                } else {
                    window.close();
                }
            }
        },

        /**
         * 根据操作获取动画class
         *
         * @param {string} name in/out
         * @returns {string} class
         */
        getAnimate: function (name) {
            return animate[name];
        },
        /**
         * animate.css 工具
         *
         * @param selector {string} 展示动画的元素选择器
         * @param animationName {string} 动画名称
         * @param callback {function} 回调函数
         */
        animateCSS: function (selector, animationName, callback) {
            let node;
            if (typeof selector === 'string') {
                node = document.querySelector(selector);
            } else {
                node = selector;
            }

            node.classList.add('animated', animationName);

            function handleAnimationEnd() {
                node.classList.remove('animated', animationName);
                node.removeEventListener('animationend', handleAnimationEnd);

                if (EUtil.isFunction(callback)) {
                    callback.call();
                }
            }

            node.addEventListener('animationend', handleAnimationEnd);
        },
        /**
         * 移除页面加载中class
         */
        removePageLoading: function () {
            $('body').removeClass(cssPrefix + 'page--loading');
        }
    };
}();
// 初始化
$(document).ready(function () {
    EApp.init();
});

"use strict";

/**
 * 业务工具类，提供了一些业务相关的公用方法
 */
let ETool = function () {
    let util = EUtil;
    let app = EApp;
    // 默认设置

    // `controller` 的 `@RequestMapping` 根路径
    let baseUrl = null;

    /**
     * 获取表单中`class`包含`auto-params`的元素，返回url所需的参数格式
     *
     * @param element {element} html 元素对象（必要）
     * @returns {string|null} eg: a=1&b=2
     */
    let getAutoParams = function (element) {
        if (typeof element !== 'undefined') {
            return $(element).parents('.e-form').find('.auto-params').serialize();
        } else {
            return null;
        }
    };

    /**
     * 验证url是否为空,如为空根据（method）默认规则生成
     *
     * @param url {string|null} url
     * @param method {string} 方法名（必要）
     * @param suffix {string|null} 后缀
     * @returns {string|null} 处理后的url
     */
    let getUrl = function (url, method, suffix) {
        if (util.isBlank(url)) {
            if (checkBaseUrl()) {
                url = baseUrl + urlSuffix[method] + (util.isNotBlank(suffix) ? suffix : '');
            } else {
                return null;
            }
        }
        return url;
    };

    /**
     * 检查业务通用url部分是否已设置
     *
     * @returns {boolean} true/false
     */
    let checkBaseUrl = function () {
        if (util.isNotBlank(baseUrl)) {
            return true;
        } else {
            printWarn('请使用ETool.setBaseUrl(\'string\');设置业务url根目录');
            return false;
        }
    };

    /**
     * 获取操作按钮class
     *
     * @param type {string} 按钮类型 eg：success、info、warning、danger、brand、dark、light  (默认：success)
     * @returns {string} class
     */
    let getActionsBtnClass = function (type) {
        if (util.isBlank(type)) {
            type = 'success';
        }
        return 'table-actions btn btn-sm btn-icon btn-icon-md btn-light-hover-' + type;
    };

    /**
     * 根据用户偏好设置判断是否使用弹框提示
     *
     * @returns {boolean} true/false
     */
    let useSimpleTip = function () {
        let simpleTip = true;
        let cacheSettings = ETool.getCache('preference-settings');
        if (EUtil.isNotBlank(cacheSettings)) {
            simpleTip = $.parseJSON(cacheSettings)['simpleTip'];
        }
        return simpleTip;
    };
    /**
     * 输出警告信息（限调试模式）
     *
     * @param str {string} 警告信息（必要）
     */
    let printWarn = function (str) {
        if (EDebug) {
            console.warn(str);
        }
    };

    return {
        /**
         * 设置业务 base url
         *
         * @param url {string} 访问地址（必要）
         */
        setBaseUrl: function (url) {
            baseUrl = url;
        },

        /**
         * 获取业务 base url
         *
         * @return {string} 业务 base url
         */
        getBaseUrl: function () {
            return baseUrl;
        },

        /**
         * 打开导入页面
         *
         * @param importCode {string} `系统管理>系统设置>导入模板中的模板代码`（必要）
         */
        importData: function (importCode) {
            app.openPage('导入数据', basePath + '/auth/sys/import/excel/data/' + importCode);
        },

        /**
         * 导出数据
         *
         * @param element {element} html 元素对象（必要）
         * @param url {string|null} 请求地址 (默认：规则生成)
         */
        exportData: function (element, url) {
            util.setButtonWait(element);
            let $form = $(element).parents('.e-form');
            if (util.isBlank(url)) {
                // 检查&获取请求地址
                url = getUrl(url, 'exportData', null);
                if (util.isBlank(url)) {
                    util.offButtonWait(element);
                    return;
                }
            }
            if (url.indexOf('?') > -1) {
                url += '&' + $form.serialize()
            } else {
                url += '?' + $form.serialize()
            }
            // 下载文件
            ETool.downloadFile(url);
            util.offButtonWait(element);
        },

        /**
         * 保存数据，一般用于表单页面
         *
         * @param element {element} html 元素对象 （必要）
         * @param url {string|null} 请求地址 （默认：规则生成）
         * @param needAlert {boolean|null} 是否需要弹出处理结果提示 （默认：true）
         * @param needValidate {boolean|null} 是否需要表单验证 （默认：true）
         * @param callback {function|null} 回调函数
         */
        saveData: function (element, url, needAlert, needValidate, callback) {
            if (needAlert == null) needAlert = true;
            if (needValidate == null) needValidate = true;
            let $form = $(element).parents('.e-form');
            // 如果不需要验证或者表单验证通过
            if (!needValidate || $form.valid()) {
                util.setButtonWait(element);
                if ($form != null && $form.length > 0) {
                    if (util.isBlank(url)) {
                        // 检查&获取请求地址
                        url = getUrl(url, 'saveData', null);
                        if (util.isBlank(url)) {
                            util.offButtonWait(element);
                            return;
                        }
                    }
                    // 保存
                    util.ajax({
                        url: url,
                        data: $form.serialize(),
                        error: function (XMLHttpRequest, textStatus, errorThrown) {
                            util.offButtonWait(element);
                            util.ajaxErrorCallback(XMLHttpRequest, textStatus, errorThrown);
                        },
                        fail: function () {
                            util.offButtonWait(element);
                        },
                        success: function (res) {
                            util.offButtonWait(element);
                            if (needAlert) {
                                let $id = $form.find('#id');
                                if ($id != null && $id.length > 0) {
                                    // 根据表单中`#id`的value属性判断是新增还是修改
                                    if (util.isBlank($id.val())) {
                                        ETool.successTip(commonTips.success, commonTips.save.add);
                                        $id.val(res.data.id);
                                    } else {
                                        ETool.successTip(commonTips.success, commonTips.save.update);
                                    }
                                } else {
                                    ETool.successTip(commonTips.success, commonTips.save.default);
                                }
                            }
                            if (util.isFunction(callback)) {
                                callback(res);
                            }
                        }
                    });
                } else {
                    printWarn('ETool.saveData() -> 缺少表单元素');
                }
            }
        },

        /**
         * 保存并关闭，注: 方法不支持回调函数
         *
         * @param element {element} html 元素对象（必要）
         * @param url {string|null} 请求地址 (默认：规则生成)
         * @param needAlert {boolean|null} 是否需要弹出处理结果提示 (默认：true)
         * @param needValidate {boolean|null} 是否需要表单验证 (默认：true)
         */
        saveAndClose: function (element, url, needAlert, needValidate) {
            ETool.saveData(element, url, needAlert, needValidate, function () {
                app.closeCurrentPage();
            });
        },

        /**
         * 打开新增页面，在列表页中使用，调用前请确保设置了`baseUrl`变量
         *
         * @param element {element} element元素（必要）
         * @param name {string|null} tab页名称 （默认: 新增数据）
         * @param url {string|null} 请求地址，用于自定义保存url （默认：规则生成）
         * @param pId {string|null} 父Id，用于需要在url后拼接 `/add/pId`场景
         */
        addData: function (element, name, url, pId) {
            if (typeof element !== 'undefined') {
                util.setButtonWait(element);
            }
            // 检查&获取请求地址
            if (util.isBlank(url)) {
                url = getUrl(url, 'add', pId);
                if (util.isBlank(url)) return;
            }
            // 获取页面中需要添加到url后的参数
            let params = getAutoParams(element);
            if (util.isNotBlank(params)) {
                url += '?' + params;
            }
            // 标签页名称
            if (util.isBlank(name)) {
                name = $(element).text();
                if (util.isBlank(name)) {
                    name = '新增数据';
                } else {
                    name = name.trim();
                }
            }
            app.openPage(name, url);
            if (typeof element !== 'undefined') {
                util.offButtonWait(element);
            }
        },

        /**
         * 根据数据id删除数据，一般用于表格最后操作列
         *
         * @param element {element} html 元素对象（必要）
         * @param id {string} 要删除数据的id（必要）
         * @param url {string|null} 请求地址，用于自定义删除url （默认：规则生成）
         * @param needAlert {boolean|null} 是否需要弹出处理结果提示 （默认：true）
         * @param callback {function|null} 回调函数
         */
        deleteById: function (element, id, url, needAlert, callback) {
            util.alertConfirm(commonTips.delete.title, commonTips.delete.subtitle, function () {
                if (typeof needAlert === 'undefined') needAlert = true;
                let row = $(element).parents('tr.e-datatable__row');
                let $dataTable = $(element).parents('.e-form').find('.e-datatable');
                // 检查&获取请求地址
                if (util.isBlank(url)) {
                    url = getUrl(url, 'deleteById', id);
                    if (util.isBlank(url)) return;
                }
                util.ajax({
                    wait: row,
                    url: url,
                    success: function (res) {
                        // 弹出提示
                        if (needAlert) {
                            ETool.successTip(commonTips.success, commonTips.delete.success);
                        }
                        // 刷新数据
                        ETool.selectData(element);
                        // 回调函数
                        if (util.isFunction(callback)) {
                            callback(res);
                        }
                    }
                });
            });
        },

        /**
         * 删除数据，用于列表页删除数据，支持单个或批量操作
         *
         * @param element {element} html 元素对象（必要）
         * @param id {string|null} 要删除的id，如果为空则从表格中获取勾选的数据（默认：获取表格中勾选的数据）
         * @param url {string|null} 请求地址，用于自定义删除url（默认：规则生成）
         * @param needAlert {boolean|null} 是否需要弹出处理结果提示（默认：true）
         * @param callback {function|null}  回调函数
         */
        deleteData: function (element, id, url, needAlert, callback) {
            let $dataTable = $(element).parents('.e-form').find('.e-datatable');
            // 检查&获取数据id
            if (util.isBlank(id)) {
                if (typeof $dataTable !== 'undefined' && $dataTable.length > 0) {
                    // 从表格中获取已选中select
                    let ids = ETool.getSelectData($dataTable);
                    if (ETool.checkSelectDataIsNotEmpty(ids, true)) {
                        id = ids.join(',');
                    } else {
                        return;
                    }
                } else {
                    printWarn('无法获取要删除的数据id');
                    return;
                }
            }
            // 询问
            util.alertConfirm(commonTips.delete.title, commonTips.delete.subtitle, function () {
                // 禁用按钮
                util.setButtonWait(element);
                if (typeof needAlert === 'undefined') needAlert = true;
                // 检查&获取请求地址
                if (util.isBlank(url)) {
                    url = getUrl(url, 'deleteById', id);
                    if (util.isBlank(url)) {
                        util.offButtonWait(element);
                        return;
                    }
                }

                util.ajax({
                    url: url,
                    wait: $dataTable,
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        util.offButtonWait(element);
                        util.ajaxErrorCallback(XMLHttpRequest, textStatus, errorThrown);
                    },
                    fail: function () {
                        util.offButtonWait(element);
                    },
                    success: function (res) {
                        util.offButtonWait(element);
                        // 弹出提示
                        if (needAlert) {
                            ETool.successTip(commonTips.success, commonTips.delete.success);
                        }
                        // 刷新数据
                        ETool.selectData(element);
                        // 回调函数
                        if (util.isFunction(callback)) {
                            callback(res);
                        }
                    }
                });
            });
        },

        /**
         * 根据数据id修改数据，一般用于表格最后操作列
         *
         * @param element {element} html 元素对象（必要）
         * @param id {string} 要修改数据的id（必要）
         * @param name {string|null} tab页名称（默认: 修改）
         * @param url {string|null} 请求地址，用于自定义修改url （默认：规则生成）
         * @param callback {function|null} 回调函数
         */
        editById: function (element, id, name, url, callback) {
            url = getUrl(url, 'input', id);
            if (util.isBlank(url)) return;
            if (util.isBlank(name)) {
                name = $(element).text();
                if (util.isBlank(name)) {
                    name = '修改数据';
                }
            }
            app.openPage(name, url);
            // 回调函数
            if (util.isFunction(callback)) {
                callback(res);
            }
        },

        /**
         * 查询数据，一般用于列表页
         *
         * @param element {element} html 元素对象（必要）
         */
        selectData: function (element) {
            let $dataTable = $(element).parents('.e-form').find('.e-datatable');
            if (typeof $dataTable !== 'undefined' && $dataTable.length > 0) {
                $dataTable.EDataTable().reload();
            }
        },

        /**
         * 初始化DataTable，文档请前往[DataTable](http://www.easy-frame.top/guide/plugins/table/table.html)查看
         *
         * @param options {object} 配置（必要）
         * @returns {*|jQuery}
         */
        initDataTable: function (options) {
            // 检查&获取请求地址
            let url = options.url;
            if (util.isBlank(url)) {
                url = getUrl(options.url, 'select', null);
                delete options['url'];
                if (util.isBlank(url)) return;
            }

            /**
             * 默认设置
             */
            let _defaultOptions = {
                selector: '.e-datatable',
                // 数据源
                data: {
                    type: 'remote',
                    source: {
                        autoQuery: true, // 带入表单参数
                        read: {
                            url: url,
                            map: function (res) {
                                if (typeof res.data.records !== 'undefined') { // 带有分页信息
                                    return res.data.records;
                                } else {
                                    return res.data;
                                }
                            }
                        }
                    },
                    saveState: {
                        // 使用cookie/webstorage 保存表格状态(分页, 筛选, 排序)
                        cookie: false,
                        webstorage: true
                    },
                    pageSize: EDefaultOptions.dataTable.page.size, // 页大小
                    serverPaging: true, // 在服务器进行数据分页
                    serverFiltering: true, // 在服务器进行数据过滤
                    serverSorting: true // 在服务器进行数据排序
                },
                toolbar: {
                    items: {
                        pagination: {
                            pagination: [10, 15, 20, 30, 50, 100, -1]
                        }
                    }
                },
                // 布局
                layout: {
                    theme: 'default', // 主题
                    class: '', // 自定义class
                    scroll: true, // 启用滚动条
                    height: EDefaultOptions.dataTable.layout.height, // 高度
                    footer: false // 显示/隐藏 footer
                },
                search: {
                    // 查询条件(仅用于数据在local)
                    input: '.query-modular input, .query-modular select',
                    // 在输入框按回车查询
                    onEnter: true
                },
                // 列滚动
                sortable: true,
                // 分页
                pagination: true
            };
            // 合并配置
            options = $.extend(true, {}, _defaultOptions, options);

            let eDataTable = $(options.selector).EDataTable(options);
            let $form = $(options.selector).parents('form.e-form');
            if (typeof $form !== 'undefined' && $form.length > 0) {
                // 如果有查询按钮,绑定点击重新加载数据事件
                let $searchBtn = $form.find('.btn-search');
                if (typeof $searchBtn !== 'undefined' && $searchBtn.length > 0) {
                    $searchBtn.click(function () {
                        // 查询并返回第一页
                        eDataTable.gotoPage(1);
                    });
                }
                // 如果有重置按钮,绑定点击重置事件
                let $resetBtn = $form.find('.btn-reset');
                if (typeof $resetBtn !== 'undefined' && $resetBtn.length > 0) {
                    $resetBtn.click(function () {
                        $form.resetForm();
                        let $selectPicker = $form.find('.select-picker');
                        if (typeof $selectPicker !== 'undefined' && $selectPicker.length > 0) {
                            $selectPicker.trigger("change");
                        }
                    });
                }

                // 绑定表格工具事件
                // 恢复默认排序
                $form.find('.e-default-order').click(function (e) {
                    e.preventDefault();
                    eDataTable.recoveryDefaultOrder();
                });
                // 恢复默认页大小
                $form.find('.e-default-page-size').click(function (e) {
                    e.preventDefault();
                    eDataTable.recoveryDefaultPageSize();
                });
                // 全部恢复全部
                $form.find('.e-default-all').click(function (e) {
                    e.preventDefault();
                    eDataTable.recoveryDefaultAll();
                });
            }

            return eDataTable;
        },

        /**
         * 将表单数据序列化为json对象
         *
         * @param $form {jQuery} 表单（必要）
         * @returns {null|object} eg: {a: 1, b: 2}
         */
        queryParams: function ($form) {
            let data = $form.serializeArray();
            if (typeof data !== 'undefined' && data.length > 0) {
                let params = {};
                for (let i = 0; i < data.length; i++) {
                    if (util.isNotBlank(data[i].value)) {
                        if (util.isNotBlank(params[data[i].name])) {
                            params[data[i].name] = params[data[i].name] + ',' + data[i].value;
                        } else {
                            params[data[i].name] = data[i].value;
                        }
                    }
                }
                return params;
            } else {
                return null;
            }
        },

        /******************** 提示 ********************/
        /**
         * `info` 提示，
         * 会根据用户偏好设置中设置的提示方式进行提示
         *
         * @param title {string} 标题（必要）
         * @param subtitle {string} 副标题
         */
        infoTip: function (title, subtitle) {
            if (useSimpleTip()) {
                util.toastr({
                    type: 'info',
                    title: title,
                    subtitle: subtitle
                });
            } else {
                util.alertInfo(title, subtitle);
            }
        },

        /**
         * `success` 提示，
         * 会根据用户偏好设置中设置的提示方式进行提示
         *
         * @param title {string} 标题（必要）
         * @param subtitle {string} 副标题
         */
        successTip: function (title, subtitle) {
            if (useSimpleTip()) {
                util.toastr({
                    type: 'success',
                    title: title,
                    subtitle: subtitle
                });
            } else {
                util.alertSuccess(title, subtitle);
            }
        },
        /**
         * `warning`提示，
         * 会根据用户偏好设置中设置的提示方式进行提示
         *
         * @param title {string} 标题（必要）
         * @param subtitle {string} 副标题
         */
        warnTip: function (title, subtitle) {
            if (useSimpleTip()) {
                util.toastr({
                    type: 'warning',
                    title: title,
                    subtitle: subtitle
                });
            } else {
                util.alertWarning(title, subtitle);
            }
        },
        /**
         * `error`提示，
         * 会根据用户偏好设置中设置的提示方式进行提示
         *
         * @param title {string} 标题（必要）
         * @param subtitle {string} 副标题
         */
        errorTip: function (title, subtitle) {
            if (useSimpleTip()) {
                util.toastr({
                    type: 'error',
                    title: title,
                    subtitle: subtitle
                });
            } else {
                util.alertError(title, subtitle);
            }
        },

        /******************** 字典 ********************/
        /**
         * 根据字典类型获取字典数组
         *
         * @param dictType {string} 字典类型（必要）
         * @returns {array|null} 如没有返回`null` eg：[{...}, {...}]
         */
        getSysDictArrayByDictType: function (dictType) {
            if (typeof sysDict !== 'undefined' && util.isNotBlank(dictType)) {
                return sysDict[dictType];
            }
            return null;
        },
        /**
         * 根据字典类型获取字典对象
         *
         * @param dictType {string} 字典类型（必要）
         * @returns {object|null} 如没有返回`null` eg：{1 : {...}, 2: {...}}
         */
        getSysDictsObjectByDictType: function (dictType) {
            let dicts = ETool.getSysDictArrayByDictType(dictType);
            if (dicts != null && dicts.length > 0) {
                let dictsObject = {};
                $(dicts).each(function (index, dict) {
                    dictsObject[dict.code] = dict;
                });
                return dictsObject;
            }
            return null;
        },

        /**
         * 根据字典类型与编码获取字典信息
         *
         * @param dictType {string} 字典类型（必要）
         * @param code {string} 代码（必要）
         * @returns {object|null} 字典对象或`null`
         */
        getSysDictObjectByQuery: function (dictType, code) {
            let dicts = ETool.getSysDictArrayByDictType(dictType);
            if (dicts != null && dicts.length > 0) {
                for (let i = 0; i < dicts.length; i++) {
                    if (dicts[i].code == code) {
                        return dicts[i];
                    }
                }
            }
            return null;
        },

        /**
         * 根据字典类型与编码获取字典名称
         *
         * @param dictType {string} 字典类型（必要）
         * @param code {string} 编码（必要）
         * @returns {string|null} 字典名称或`null`
         */
        getSysDictNameByQuery: function (dictType, code) {
            let dict = ETool.getSysDictObjectByQuery(dictType, code);
            if (dict != null) {
                return dict.name;
            }
            return null;
        },

        /**
         * 将字典转为html元素（徽章）
         *
         * @param code {string} 字典编码（必要）
         * @param dict {object} 字典对象（必要）
         * @returns {string} eg: `<span class="xxx">xxx</span>`
         */
        getDictElement: function (code, dict) {
            let cur_dict = dict[code];
            if (cur_dict == null) {
                // 如果没有对应的字典,检查已知字典是否设置了css
                if (dict != null && dict.length > 0 && util.isNotBlank(dict[0].css)) {
                    cur_dict = {
                        css: 'badge badge--success badge--inline badge--pill badge--rounded',
                        name: code
                    }
                }
            }
            if (cur_dict != null) {
                if (util.isNotBlank(cur_dict.css)) {
                    return '<span class="' + cur_dict.css + '">' + cur_dict.name + '</span>';
                } else {
                    return cur_dict.name;
                }
            } else {
                return code;
            }
        },

        /********************
         ** 用户权限
         ********************/
        /**
         * 获取当前登录用户，默认优先从缓存获取，如不希望从缓存中获取可设置`cache: false`
         *
         * @param cache {boolean|null} 是否使用缓存数据 （默认：true）
         * @return {Object} 当前登录用户信息
         */
        getUser: function (cache) {
            /**
             * 从后端获取当前登录用户
             *
             * @returns {*}
             */
            let _getUser = function () {
                let data = {};

                /**
                 * 将attr:attr数组转为对象
                 * @param data
                 * @returns {object}
                 */
                let arrayToObject = function (data) {
                    let obj = {};
                    if (util.isArray(data)) {
                        $(data).each(function (i, _obj) {
                            let levels = _obj.split(':'), _i = 0;

                            function createLevel(child) {
                                let name = levels[_i++];
                                if (typeof child[name] !== 'undefined' && child[name] !== null) {
                                    if (typeof child[name] !== 'object') {
                                        child[name] = {};
                                    }
                                } else {
                                    child[name] = {};
                                }
                                if (_i === levels.length) {
                                    child[name] = true;
                                } else {
                                    createLevel(child[name]);
                                }
                            }

                            createLevel(obj);
                        });
                        return obj;
                    } else {
                        return null;
                    }
                };

                util.ajax({
                    async: false,
                    url: basePath + '/auth/sys/user/current',
                    // url: basePath + '/assets/data/user.json',
                    type: 'get',
                    success: function (res) {
                        data = res.data;
                    }
                });
                if (typeof data.roleList !== 'undefined') {
                    data.role = arrayToObject(data.roleList);
                }
                if (typeof data.permissionList !== 'undefined') {
                    data.permissions = arrayToObject(data.permissionList);
                }
                return data;
            };

            let user = null;
            if (typeof cache === 'undefined' || cache) {
                user = ETool.getCache(EDefaultOptions.currentUser);
                if (user == null) {
                    user = _getUser();
                    ETool.setCache(EDefaultOptions.currentUser, user);
                } else {
                    user = $.parseJSON(user);
                }
            } else {
                user = _getUser();
                ETool.setCache(EDefaultOptions.currentUser, user);
            }
            return user;
        },

        /**
         * 当前登录用户是否拥有指定权限标识
         *
         * @param code {string} 权限标识（必要）
         * @return {boolean} true/false
         */
        hasPermissions: function (code) {
            let user = ETool.getUser(true);
            return ETool.checkPermissions(code, user.permissions);
        },
        /**
         * 当前登录用户是否没有指定权限标识
         *
         * @param code {string} 权限标识（必要）
         * @return {boolean} true/false
         */
        notHasPermissions: function (code) {
            return !ETool.hasPermissions(code);
        },
        /**
         * 当前登录用户是否属于指定角色标识
         *
         * @param code {string} 角色标识（必要）
         * @return {boolean} true/false
         */
        hasRole: function (code) {
            let user = ETool.getUser(true);
            return ETool.checkPermissions(code, user.role);
        },
        /**
         * 当前登录用户是否不属于指定角色标识
         *
         * @param code {string} 角色标识（必要）
         * @return {boolean} true/false
         */
        notHasRole: function (code) {
            return !ETool.hasRole(code);
        },
        /**
         * 检查权限
         *
         * @param code {string} 权限标识（必要）
         * @param permissions {object} 权限对象（必要）
         * @return {boolean} true/false
         */
        checkPermissions: function (code, permissions) {
            if (util.isNotBlank(code) && permissions != null && typeof permissions === 'object') {
                return Boolean(ETool.getObject(code, permissions, ':'));
            }
            return false;
        },
        /**
         * 获取cookies/localStorage中的对象
         *
         * @param key {string} key（必要）
         * @return {object} value
         */
        getCache: function (key) {
            let obj;
            if (localStorage) {
                obj = localStorage.getItem(key);
            }
            if (obj == null) {
                obj = Cookies.get(key);
            }
            return obj;
        },
        /**
         * 设置cookies/localStorage中的对象
         *
         * @param key {string} key（必要）
         * @param value {object} value（必要）
         */
        setCache: function (key, value) {
            if (util.isNotBlank(key)) {
                if (typeof value === 'object') {
                    value = JSON.stringify(value);
                }
            }
            if (localStorage) {
                localStorage.setItem(key, value);
            }
            Cookies.set(key, value);
        },

        /**
         * 移除指定cookies/localStorage中的对象
         *
         * @param key {string} key（必要）
         */
        cacheRemove: function (key) {
            if (localStorage) {
                localStorage.removeItem(key);
            }
            Cookies.remove(key);
        },
        /********************  工具  *********************/
        /**
         * 根据属性路径获取object中的value
         *
         * @param path {string} 属性路径 eg：`user.depart.name`（必要）
         * @param object {object} 对象（必要）
         * @param separate {string|null} 分隔符（默认：.）
         * @returns {object|null} 如果未找到返回`null`
         */
        getObject: function (path, object, separate) {
            if (util.isBlank(separate)) {
                separate = '.';
            }
            return path.split(separate).reduce(function (obj, i) {
                return obj !== null && typeof obj[i] !== 'undefined' ? obj[i] : null;
            }, object);
        },
        /**
         * 获取操作按钮class
         *
         * @param type {string} 按钮类型（必要）
         * @returns {string}
         */
        getActionsBtnClass: function (type) {
            return getActionsBtnClass(type);
        },
        /**
         * 下载文件
         *
         * @param url {string} 下载地址（必要）
         */
        downloadFile: function (url) {
            let form = document.createElement('form');
            form.setAttribute('action', url);
            form.setAttribute('method', 'post');
            form.setAttribute('style', 'display:none');
            document.body.appendChild(form);
            form.submit();
            document.body.removeChild(form);
        },
        /**
         * 解析响应码，响应码规则请参考[响应码](/guide/standard.html#%E5%93%8D%E5%BA%94%E7%A0%81)
         *
         * @param code {string} 响应码（必要）
         * @returns {{modular: string, code: string, level: string} | string}
         */
        analysisCode: function (code) {
            let result = code;
            // 响应码必须为`resultCode.length`位字符串，如果不是则直接返回传入值
            if (util.isNotBlank(code) && code.length === resultCode.length) {
                result = {
                    // 级别
                    level: code.substr(0, 1),
                    // 模块代码
                    modular: code.substr(1, 2),
                    // 错误代码
                    code: code.substr(3, 3)
                };
            }
            return result;
        },
        ACTIONS_INFO: getActionsBtnClass('info'),
        ACTIONS_SUCCESS: getActionsBtnClass('success'),
        ACTIONS_WARN: getActionsBtnClass('warning'),
        ACTIONS_DANGER: getActionsBtnClass('danger'),
        /******************** jsTree ********************/

        /**
         * 保存/修改jsTree的节点信息
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         * @param node {object} 节点信息（必要）
         */
        saveNode: function (tree, node) {
            tree = ETool.getTree(tree);
            let _node = tree.get_node(node.id, false);
            if (_node != null && _node) { // 节点存在, 更新节点名称、图标
                if (util.isString(node.type)) {
                    tree.set_type(_node, node.type);
                }
                if (util.isString(node.text)) {
                    tree.rename_node(_node, node.text);
                }
                if (util.isString(node.icon)) {
                    tree.set_icon(_node, node.icon);
                }
            } else {
                let pNode = tree.get_node(node.pId, false); // 查找父节点
                if (tree.is_open(pNode) || tree.is_leaf(pNode)) {// 父节点已展开并且没有子节点
                    tree.create_node(pNode, node);//创建节点
                    if (!tree.is_open(pNode)) { // 如果节点没展开,则展开
                        tree.open_node(pNode);
                    }
                } else {
                    tree.open_node(pNode); // 展开父节点,异步加载数据
                }
            }
        },
        /**
         * 删除jsTree的节点
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         * @param ids {number|string} 要删除的id（必要）
         */
        deleteNode: function (tree, ids) {
            tree = ETool.getTree(tree);
            if (util.isNumber(ids)) {
                tree.delete_node(tree.get_node(ids, false));
            } else if (util.isString(ids)) {
                ETool._deleteNode(tree, ids.split(','));
            } else if (util.isArray(ids)) {
                ETool._deleteNode(tree, ids);
            }
        },
        /**
         * 删除jsTree的指定节点
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         * @param ids {array} 要删除的节点id（必要）
         */
        _deleteNode: function (tree, ids) {
            $(ids).each(function (i, id) {
                tree.delete_node(tree.get_node(id, false));
            });
        },
        /**
         * 右键点击时获取用户想操作的jsTree节点 <br/>
         * 1.右键点击的节点属于多选节点, 返回所有已选中节点 <br/>
         * 2.右键点击的节点不属于多选的节点, 返回右键点击节点并选中 <br/>
         * @param data {object}（必要）
         */
        getOperationNodes: function (data) {
            let tree = $.jstree.reference(data.reference);
            let cur_click = tree.get_node(data.reference);
            let selectNodes = tree.get_selected();
            for (let i = 0; i < selectNodes.length; i++) {
                if (selectNodes[i] == cur_click.id) {
                    return selectNodes;
                }
            }
            tree.deselect_all();
            tree.select_node(cur_click);
            return [cur_click.id];
        },
        /**
         * 获取右键点击的jsTree节点
         * @param data {object}（必要）
         * @returns {*|Object|jQuery|void}
         */
        getClickNode: function (data) {
            let tree = $.jstree.reference(data.reference);
            return tree.get_node(data.reference);
        },
        /**
         * 获取jsTree选中节点
         * 注: id='#' 节点会被过滤
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         * @param attr 要获取的属性（默认：返回节点全部信息）
         * @returns {array}
         */
        getCheckedNodes: function (tree, attr) {
            tree = ETool.getTree(tree);
            let checked = [];
            for (let node in tree._model.data) {
                if (tree.is_undetermined(node) || tree.is_checked(node)) {
                    if (typeof tree._model.data[node]['id'] === 'undefined' || tree._model.data[node]['id'] != '#') {
                        checked.push(util.isNotBlank(attr) ? tree._model.data[node][attr] : tree._model.data[node]);
                    }
                }
            }
            return checked;
        },
        /**
         * jsTree 设置节点选中
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         * @param values {array|string} 选中的值,数组或字符串(1,2,3)（必要）
         */
        checkNodes: function (tree, values) {
            tree = ETool.getTree(tree);
            // 暂时禁用级联,防止选中父节点后全选子节点
            let cascade = tree.settings.checkbox.cascade;
            tree.settings.checkbox.cascade = '';
            if (typeof values === 'string') {
                values = values.split(',');
            }
            tree.check_node(values);
            // 恢复级联
            tree.settings.checkbox.cascade = cascade;
        },
        /**
         * jsTree 全选
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         */
        checkAll: function (tree) {
            tree = ETool.getTree(tree);
            tree.check_all();
        },
        /**
         * jsTree 全不选
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         */
        unCheckAll: function (tree) {
            tree = ETool.getTree(tree);
            tree.uncheck_all();
        },
        /**
         * 获取jsTree对象
         *
         * @param tree {object|string} 选择器或者jsTree对象（必要）
         * @returns {object}
         */
        getTree: function (tree) {
            if (typeof tree === 'string') {
                tree = $(tree).jstree(true);
            }
            return tree;
        },

        /**
         * 获取勾选的数据
         *
         * @param $dataTable {object} dataTable 对象（必要）
         * @returns {Array} eg：[1,2,3]
         */
        getSelectData: function ($dataTable) {
            return $dataTable.EDataTable().getValue();
        },
        /**
         * 检查ids是否为空
         *
         * @param ids {array} ids（必要）
         * @param tip {boolean} 是否需要弹出提示`commonTips.noIds`；（默认`false`）
         * @returns {boolean} true/false
         */
        checkSelectDataIsNotEmpty: function (ids, tip) {
            if (ids == null || ids.length === 0) {
                if (tip) {
                    ETool.warnTip(commonTips.fail, commonTips.noIds);
                }
                return false;
            } else {
                return true;
            }
        },
    };
}();
ETool.commonDict = ETool.getSysDictsObjectByDictType('commonStatus');
window.EUtil = EUtil;

"use strict";
// 卡片
let ECard = function (selector, options) {
    // 目标元素
    let the = this;

    // 查找对象
    let $element = $(selector);
    let element = $element[0];
    let $body = $('body');

    let stickyClass = 'card--sticky';

    if (!element) {
        return;
    }

    // 默认设置
    let defaultOptions = {
        bodyToggleSpeed: 400,
        tooltips: true,
        tools: {
            toggle: {
                collapse: {
                    text: '折叠',
                    icon: 'la la-angle-up'
                },
                expand: {
                    text: '展开',
                    icon: 'la la-angle-down'
                }
            },
            reload: '刷新',
            remove: '移除',
            fullscreen: {
                on: {
                    text: '全屏',
                    icon: 'la la-compress'
                },
                off: {
                    text: '退出全屏',
                    icon: 'la la-expand'
                }
            }
        },
        sticky: {
            offset: 300,
            zIndex: 101
        }
    };

    // 私有方法
    let Plugin = {
        construct: function (options) {
            if (EUtil.data(element).has('card')) {
                the = EUtil.data(element).get('card');
            } else {
                // 初始化
                Plugin.init(options);

                // 构建
                Plugin.build();

                EUtil.data(element).set('card', the);
            }

            return the;
        },

        /**
         * 初始化
         */
        init: function (options) {
            the.element = element;
            the.events = [];

            // 合并默认选项&传入option
            the.options = $.extend(true, {}, defaultOptions, options);
            // head body foot
            the.head = $element.find('.card-head');
            the.body = $element.find('.card-body');
            the.foot = $element.find('.card-foot');
        },

        /**
         * 构建
         */
        build: function () {
            // 绑定 展开/折叠、刷新、删除、全屏/退出全屏 事件
            the.head.find('[data-card-tool="toggle"], [data-card-tool=reload], [data-card-tool=remove], [data-card-tool=fullscreen]').click(function (e) {
                e.preventDefault();
                let method = $(this).data('card-tool');
                Plugin[method].call();
            });
            // 设置tooltips
            Plugin.setupTooltips();
        },

        /**
         * 初始化固定头部
         */
        initSticky: function () {
            if (the.head.length) {
                window.addEventListener('scroll', Plugin.onScrollSticky);
            }
        },

        /**
         * 当滚动到头部时将头部固定在顶部，一般用于头部有操作按钮的卡片
         * 注：仅支持页面中只有一个card的场景
         */
        onScrollSticky: function (e) {
            let offset = the.options.sticky.offset;
            if (isNaN(offset)) return;
            // 页面往上滚动的距离
            let st = document.documentElement.scrollTop;

            if (st >= offset && !$body.hasClass(stickyClass)) {
                Plugin.eventTrigger('stickyOn');
                $body.addClass(stickyClass);
                $element.addClass(stickyClass);
                Plugin.updateSticky();

            } else if ((st * 1.5) <= offset && $body.hasClass(stickyClass)) {
                // 往上滚动到offset，取消固定
                Plugin.eventTrigger('stickyOff');
                $body.removeClass(stickyClass);
                $element.removeClass(stickyClass);
                Plugin.resetSticky();
            }
        },

        updateSticky: function () {
            if (the.head.length) {
                let top;

                if ($body.hasClass(stickyClass)) {
                    if (the.options.sticky.position.top instanceof Function) {
                        top = parseInt(the.options.sticky.position.top.call());
                    } else {
                        top = parseInt(the.options.sticky.position.top);
                    }

                    let left;
                    if (the.options.sticky.position.left instanceof Function) {
                        left = parseInt(the.options.sticky.position.left.call());
                    } else {
                        left = parseInt(the.options.sticky.position.left);
                    }

                    let right;
                    if (the.options.sticky.position.right instanceof Function) {
                        right = parseInt(the.options.sticky.position.right.call());
                    } else {
                        right = parseInt(the.options.sticky.position.right);
                    }
                    the.head.css({
                        zIndex: the.options.sticky.zIndex,
                        top: top,
                        left: left,
                        right: right
                    });
                }
            }
        },
        /**
         * 取消固定
         */
        resetSticky: function () {
            if (the.head.length && !$body.hasClass(stickyClass)) {
                the.head.css({
                    zIndex: '',
                    top: '',
                    left: '',
                    right: ''
                });
            }
        },

        /**
         * 删除卡片
         */
        remove: function () {
            // 删除前回调
            if (Plugin.eventTrigger('beforeRemove') === false) {
                return;
            }

            if ($body.hasClass('card--fullscreen') && $element.hasClass('card--fullscreen')) {
                // 如果处于全屏状态，退出全屏
                Plugin.fullscreen('off');
            }

            Plugin.removeTooltips();

            $element.remove();
            // 删除后回调
            Plugin.eventTrigger('afterRemove');
        },

        /**
         * 设置 tooltips
         */
        setupTooltips: function () {
            // 公用属性
            let commonOption = {};
            /**
             *
             * @param _selector {string} 选择器
             * @param title {string} 标题
             */
            let initTooltip = function (_selector, title) {
                let $target = the.head.find(_selector);
                if ($target.length) {
                    let tip = new Tooltip($target, $.extend(true, {}, commonOption, {
                        title: title
                    }));
                    EUtil.data($target).set('tooltip', tip);
                }
            };

            if (the.options.tooltips) {
                // 是否为折叠状态
                let collapsed = $element.hasClass('card--collapse') || $element.hasClass('card--collapsed');
                // 是否为全屏状态
                let fullscreenOn = $body.hasClass('card--fullscreen') && $element.hasClass('card--fullscreen');
                // 方向
                let placement = fullscreenOn ? 'bottom' : 'top';
                // 公用属性
                commonOption = {
                    placement: placement,
                    offset: fullscreenOn ? '0,10px,0,0' : '0,5px',
                    trigger: 'hover',
                    template: '<div class="tooltip tooltip-card tooltip bs-tooltip-' + placement + '" role="tooltip">\
                            <div class="tooltip-arrow arrow"></div>\
                            <div class="tooltip-inner"></div>\
                        </div>'
                };
                // 展开/折叠
                initTooltip('[data-card-tool=toggle]', collapsed ? the.options.tools.toggle.expand.text : the.options.tools.toggle.collapse.text);
                //刷新
                initTooltip('[data-card-tool=reload]', the.options.tools.reload);
                //删除
                initTooltip('[data-card-tool=remove]', the.options.tools.remove);
                //全屏/退出全屏
                initTooltip('[data-card-tool=fullscreen]', (fullscreenOn ? the.options.tools.fullscreen.off.text : the.options.tools.fullscreen.on.text));
            }
        },

        /**
         * 删除 tooltips
         */
        removeTooltips: function () {
            if (the.options.tooltips) {
                the.head.find('[data-card-tool="toggle"], [data-card-tool=reload], [data-card-tool=remove], [data-card-tool=fullscreen]').each(function () {
                    let tip = EUtil.data(this).get('tooltip');
                    if (tip) {
                        tip.dispose();
                    }
                });
            }
        },

        /**
         * 刷新
         */
        reload: function () {
            Plugin.eventTrigger('reload');
        },

        /**
         * 展开/折叠
         */
        toggle: function () {
            if ($element.hasClass('card--collapse') || $element.hasClass('card--collapsed')) {
                Plugin.expand();
            } else {
                Plugin.collapse();
            }
        },

        /**
         * 折叠
         */
        collapse: function () {
            if (Plugin.eventTrigger('beforeCollapse') === false) {
                // 回调函数不允许折叠
                return;
            }

            the.body.slideUp(the.options.bodyToggleSpeed, function () {
                Plugin.eventTrigger('afterCollapse');
            });
            $element.addClass('card--collapse');

            if (the.options.tooltips) {
                let $toggle = the.head.find('[data-card-tool=toggle]');
                if ($toggle.length && EUtil.data($toggle).has('tooltip')) {
                    EUtil.data($toggle).get('tooltip').updateTitleContent(the.options.tools.toggle.expand);
                    $toggle.find('i').removeClass().addClass(the.options.tools.toggle.expand.icon);
                }
            }
        },

        /**
         * 展开
         */
        expand: function () {
            if (Plugin.eventTrigger('beforeExpand') === false) {
                // 回调函数不允许展开
                return;
            }

            the.body.slideDown(the.options.bodyToggleSpeed, function () {
                Plugin.eventTrigger('afterExpand');
            });
            $element.removeClass('card--collapse card--collapsed');

            if (the.options.tooltips) {
                let $toggle = the.head.find('[data-card-tool=toggle]');
                if ($toggle.length && EUtil.data($toggle).has('tooltip')) {
                    EUtil.data($toggle).get('tooltip').updateTitleContent(the.options.tools.toggle.collapse.text);
                    $toggle.find('i').removeClass().addClass(the.options.tools.toggle.collapse.icon);
                }
            }
        },

        /**
         * 全屏/退出全屏
         *
         * @param mode {string} on | off
         */
        fullscreen: function (mode) {
            if (!mode) {
                mode = $body.hasClass('card--fullscreen') ? 'off' : 'on';
            }

            if ('off' === mode || ($body.hasClass('card--fullscreen') && $element.hasClass('card--fullscreen'))) {
                Plugin.eventTrigger('beforeFullscreenOff');

                $body.removeClass('card--fullscreen');
                $element.removeClass('card--fullscreen');

                // Plugin.removeTooltips();
                // Plugin.setupTooltips();

                if (the.foot.length) {
                    the.foot.css({
                        marginBottom: '',
                        marginTop: ''
                    });
                }

                Plugin.eventTrigger('afterFullscreenOff');
            } else {
                Plugin.eventTrigger('beforeFullscreenOn');

                $element.addClass('card--fullscreen');
                $body.addClass('card--fullscreen');

                // Plugin.removeTooltips();
                // Plugin.setupTooltips();

                if (the.foot.length) {
                    let footHeight = the.foot.height();
                    let height = footHeight + the.head.height();
                    the.body.css('margin-bottom', footHeight);
                    the.foot.css('margin-top', height);
                }
                Plugin.eventTrigger('afterFullscreenOn');
            }

            if (the.options.tooltips) {
                let $fullscreen = the.head.find('[data-card-tool=fullscreen]');
                if ($fullscreen.length && EUtil.data($fullscreen).has('tooltip')) {
                    EUtil.data($fullscreen).get('tooltip').updateTitleContent(the.options.tools.fullscreen[mode].text);
                    $fullscreen.find('i').removeClass().addClass(the.options.tools.fullscreen[mode].icon);
                }
            }
        },
        /**
         * Set content
         */
        setContent: function (html) {
            the.body.html(html);
        },

        /**
         * Get body
         */
        getBody: function () {
            return the.body;
        },

        /**
         * Get self
         */
        getSelf: function () {
            return element;
        },

        /**
         * 触发事件
         *
         * @param name {string} 事件名称
         * @param args {object} 参数
         */
        eventTrigger: function (name, args) {
            for (let i = 0; i < the.events.length; i++) {
                let event = the.events[i];
                if (event.name === name) {
                    if (event.one) {
                        // 一次性事件
                        if (!event.fired) {
                            // 设置事件属性为已触发，下次不执行
                            the.events[i].fired = true;
                            event.handler.call(this, the, args);
                        }
                    } else {
                        event.handler.call(this, the, args);
                    }
                }
            }
        },

        /**
         * 添加事件
         *
         * @param name {string} 事件名称
         * @param handler {function} 回调
         * @param one {boolean} 是否一次性
         */
        addEvent: function (name, handler, one) {
            the.events.push({
                name: name,
                handler: handler,
                one: one,
                fired: false
            });
        }
    };

    // 公开方法

    /**
     * 设置默认option
     *
     * @param options {object} 选项
     */
    the.setDefaults = function (options) {
        defaultOptions = options;
    };

    /**
     * 删除卡片
     * @returns {ECard}
     */
    the.remove = function () {
        return Plugin.remove();
    };

    /**
     * 初始化固定头部
     * @returns {ECard}
     */
    the.initSticky = function () {
        return Plugin.initSticky();
    };

    /**
     * 更新固定头部
     * @returns {ECard}
     */
    the.updateSticky = function () {
        return Plugin.updateSticky();
    };

    /**
     * 重置固定头部
     * @returns {ECard}
     */
    the.resetSticky = function () {
        return Plugin.resetSticky();
    };

    /**
     * 销毁固定头部
     */
    the.destroySticky = function () {
        Plugin.resetSticky();
        window.removeEventListener('scroll', Plugin.onScrollSticky);
    };

    /**
     * 刷新
     * @returns {ECard}
     */
    the.reload = function () {
        return Plugin.reload();
    };

    /**
     * 设置内容
     *
     * @param html {string} 内容
     * @returns {ECard}
     */
    the.setContent = function (html) {
        return Plugin.setContent(html);
    };

    /**
     * 切换展开/折叠卡片
     * @returns {ECard}
     */
    the.toggle = function () {
        return Plugin.toggle();
    };

    /**
     * 折叠卡片
     *
     * @returns {ECard}
     */
    the.collapse = function () {
        return Plugin.collapse();
    };

    /**
     * 展开卡片
     * @returns {ECard}
     */
    the.expand = function () {
        return Plugin.expand();
    };

    /**
     * 全屏
     * @returns {ECard}
     */
    the.fullscreen = function () {
        return Plugin.fullscreen('on');
    };

    /**
     * 退出全屏
     * @returns {ECard}
     */
    the.unFullscreen = function () {
        return Plugin.fullscreen('off');
    };

    /**
     * 获取body
     * @returns {jQuery}
     */
    the.getBody = function () {
        return Plugin.getBody();
    };

    /**
     * 获取card
     * @returns {jQuery}
     */
    the.getSelf = function () {
        return Plugin.getSelf();
    };

    /**
     * 绑定事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.on = function (name, handler) {
        return Plugin.addEvent(name, handler, false);
    };
    /**
     * 绑定一次性事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.one = function (name, handler) {
        return Plugin.addEvent(name, handler, true);
    };

    // Construct plugin
    Plugin.construct.apply(the, [options]);

    return the;
};
/**
 * 序列化表单获取json数据
 */
$.fn.serializeObject = function () {
    let ojb = {};
    let array = this.serializeArray();
    $.each(array, function () {
        if (ojb[this.name]) {
            if (!ojb[this.name].push) {
                ojb[this.name] = [ojb[this.name]];
            }
            ojb[this.name].push(this.value || '');
        } else {
            ojb[this.name] = this.value || '';
        }
    });
    return ojb;
};
"use strict";
// 菜单
let EMenu = function (selector, options) {
    // 目标元素
    let the = this;

    // 查找对象
    let $element = $(selector);
    let element = $element[0];
    let $body = $('body');
    let ps = null;
    if (!$element.length) {
        return;
    }

    // 默认设置
    let defaultOptions = {
        accordion: {
            slideSpeed: 200, // 子菜单打开速度
            autoScroll: false, // 自动滚动到合适位置
            autoScrollSpeed: 1200,
            expandAll: true // 允许打开多个子菜单
        }
    };
    // 私有方法
    let Plugin = {
        construct: function (options) {
            if (EUtil.data($element).has('menu')) {
                // 如果已经初始化，直接从缓存获取对象
                the = EUtil.data($element).get('menu');
            } else {
                // 初始化
                Plugin.init(options);
                // 重置
                Plugin.reset();
                // 绑定事件
                Plugin.build();
                EUtil.data($element).set('menu', the);
            }
            return the;
        },

        /**
         * 初始化
         *
         * @param options {object} options
         */
        init: function (options) {
            // 事件
            the.events = [];

            // handlers
            the.eventHandlers = {};

            // 合并默认选项&传入option
            the.options = $.extend(true, {}, defaultOptions, options);

            the.uid = EUtil.getUniqueID();
        },
        /**
         * 更改option
         *
         * @param options {object} options
         */
        update: function (options) {
            // 事件
            the.events = [];

            // handlers
            the.eventHandlers = {};

            // 合并默认选项&传入option
            the.options = $.extend(true, {}, defaultOptions, options);

            // 重置
            Plugin.reset();
            // 绑定事件
            Plugin.build();
        },
        /**
         * 重新初始化
         */
        reload: function () {
            // 重置
            Plugin.reset();
            // 绑定事件
            Plugin.build();
        },

        /**
         * 绑定事件
         */
        build: function () {
            the.eventHandlers['event_1'] = $element.on('click', '.e-menu-toggle', Plugin.handleSubmenuAccordion);
            the.eventHandlers['event_2'] = $element.on('click', '.e-menu-item:not(.e-menu-item--submenu) > .e-menu-link:not(.e-menu-toggle):not(.e-menu-link--toggle-skip)', Plugin.handleLinkClick);

            if (the.options.scroll && the.options.scroll.height) {
                Plugin.scrollInit();
            }
        },
        /**
         * 重置
         */
        reset: function () {

        },
        /**
         * 初始化滚动条
         */
        scrollInit: function () {
            if (the.options.scroll && the.options.scroll.height) {
                EUtil.scrollDestroy(element);
                EUtil.scrollInit(element, {
                    disableForMobile: true,
                    resetHeightOnDestroy: true,
                    handleWindowResize: true,
                    height: the.options.scroll.height,
                    rememberPosition: the.options.scroll.rememberPosition
                });
            } else {
                EUtil.scrollDestroy(element);
            }
        },
        /**
         * 更新菜单滚动条
         */
        scrollerUpdate: function () {
            if (the.options.scroll && the.options.scroll.height) {
                EUtil.scrollerUpdate(element);
            } else {
                EUtil.scrollDestroy(element);
            }
        },

        /**
         * 滚动到顶部
         */
        scrollerTop: function () {
            if (the.options.scroll && the.options.scroll.height) {
                EUtil.scrollTo(element, 0);
            }
        },
        scrollTo: function($item, speed){
            EUtil.scrollTo(element, $item.offset().top - $element.offset().top, speed);
        },
        /**
         * 点击菜单链接
         */
        handleLinkClick: function (e) {
            e.preventDefault();
            Plugin.eventTrigger('linkClick', this);
        },
        handleSubmenuAccordion: function (e, ele) {
            let $item = $(ele ? ele : this);

            let $li = $($item.closest('.e-menu-item'));
            let $subMenu = $li.children('.e-menu-submenu, e-menu-inner');

            if ($li.hasClass('e-menu-item--open-always')) {
                return;
            }

            if ($li.length && $subMenu.length) {
                e.preventDefault();
                let speed = the.options.accordion.slideSpeed;

                if (!$li.hasClass('e-menu-item--open')) {
                    // 未打开
                    if (!the.options.accordion.expandMultiple) {
                        // 如果不支持打开多个
                        let $subNav = $item.closest('.e-menu-nav, .e-menu-subnav');
                        let $closables = $subNav.children('.e-menu-item.e-menu-item--open.e-menu-item--submenu:not(.e-menu-item--expanded):not(.e-menu-item--open-always)');
                        if ($subNav.length && $closables.length) {
                            let $el = $($closables[0]);
                            $closables.each(function (index, el) {
                                let $subMenu = $el.children('.e-menu-submenu');
                                if ($subMenu.length) {
                                    $subMenu.slideUp(speed, function () {
                                        EUtil.scrollUpdate(element);
                                    });

                                    $el.removeClass('e-menu-item--open');
                                }
                            });
                        }
                    }
                    // 展开对应的子菜单
                    Plugin.eventTrigger('submenuToggle', $subMenu);
                    $subMenu.slideDown(speed, function () {
                        Plugin.scrollTo($item, speed);
                    });
                    $li.addClass('e-menu-item--open');
                } else {
                    Plugin.eventTrigger('submenuToggle', $subMenu);
                    $subMenu.slideUp(speed, function () {
                        Plugin.scrollTo($item, speed);
                    });
                    $li.removeClass('e-menu-item--open');
                }
            }
        },

        /**
         * 触发事件
         *
         * @param name {string} 事件名称
         * @param args {object} 参数
         */
        eventTrigger: function (name, args) {
            for (let i = 0; i < the.events.length; i++) {
                let event = the.events[i];
                if (event.name === name) {
                    if (event.one) {
                        // 一次性事件
                        if (!event.fired) {
                            // 设置事件属性为已触发，下次不执行
                            the.events[i].fired = true;
                            event.handler.call(this, the, args);
                        }
                    } else {
                        event.handler.call(this, the, args);
                    }
                }
            }
        },

        /**
         * 添加事件
         *
         * @param name {string} 事件名称
         * @param handler {function} 回调
         * @param one {boolean} 是否一次性
         */
        addEvent: function (name, handler, one) {
            the.events.push({
                name: name,
                handler: handler,
                one: one,
                fired: false
            });
        },
        /**
         * 移除事件
         *
         * @param name {string} 事件名称
         */
        removeEvent: function (name) {
            if (the.events[name]) {
                delete the.events[name];
            }
        }
    };


    // 公开方法
    /**
     * 设置默认option
     *
     * @param options {object} 选项
     */
    the.setDefaults = function (options) {
        defaultOptions = options;
    };


    /**
     * 绑定事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.on = function (name, handler) {
        return Plugin.addEvent(name, handler);
    };
    /**
     * 绑定一次性事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.one = function (name, handler) {
        return Plugin.addEvent(name, handler, true);
    };

    Plugin.construct.apply(the, [options]);
    return the;
};
"use strict";
let EOffCanvas = function (selector, options) {
    // 目标元素
    let the = this;

    // 查找对象
    let $element = $(selector);
    let $body = $('body');

    if (!$element.length) {
        return;
    }

    // 默认设置
    let defaultOptions = {};

    // 私有方法
    let Plugin = {
        construct: function (options) {
            if (EUtil.data($element).has('offCanvas')) {
                // 如果已经初始化，直接从缓存获取对象
                the = EUtil.data($element).get('offCanvas');
            } else {
                // 初始化插件
                Plugin.init(options);
                // 绑定事件
                Plugin.build();
                EUtil.data($element).set('offCanvas', the);
            }
            return the;
        },

        /**
         * 初始化
         *
         * @param options {object} option
         */
        init: function (options) {
            // 事件
            the.events = [];

            // 合并默认选项&传入option
            the.options = $.extend(true, {}, defaultOptions, options);
            // base
            the.classBase = the.options.baseClass;
            // 显示状态
            the.classShown = the.classBase + '--on';
            // 隐藏状态
            the.classOverlay = the.classBase + '-overlay';
            // 当前显示状态
            the.state = $element.hasClass(the.classShown) ? 'shown' : 'hidden';
        },
        /**
         * 绑定事件
         */
        build: function () {
            // off-canvas toggle
            if (the.options.toggleBy) {
                if (typeof the.options.toggleBy === 'string') {
                    // 如果传入的是String，表示传入的是选择器
                    $(the.options.toggleBy).click(function (e) {
                        e.preventDefault();
                        Plugin.toggle();
                    });
                } else if (the.options.toggleBy && the.options.toggleBy[0]) {
                    // 传入的是对象
                    if (the.options.toggleBy[0].target) {
                        // 指定了target属性，将事件绑定在target元素上
                        for (let i in the.options.toggleBy) {
                            $(the.options.toggleBy[i].target).click(function (e) {
                                e.preventDefault();
                                Plugin.toggle();
                            });
                        }
                    } else {
                        for (let i in the.options.toggleBy) {
                            $(the.options.toggleBy[i]).click(function (e) {
                                e.preventDefault();
                                Plugin.toggle();
                            });
                        }
                    }
                } else if (the.options.toggleBy && the.options.toggleBy.target) {
                    $(the.options.toggleBy.target).click(function (e) {
                        e.preventDefault();
                        Plugin.toggle();
                    });
                }
            }

            // off-canvas 关闭
            let $close = $(the.options.closeBy);
            if ($close.length) {
                $close.click(function (e) {
                    e.preventDefault();
                    Plugin.hide();
                });
            }
        },
        /**
         * 当前是否是显示状态
         *
         * @returns {boolean} true/false
         */
        isShown: function () {
            return the.state === 'shown';
        },
        /**
         * 切换显示&隐藏
         */
        toggle: function () {
            Plugin.eventTrigger('toggle');
            if (Plugin.isShown()) {
                Plugin.hide(this);
            } else {
                Plugin.show(this);
            }
        },
        /**
         * 显示
         *
         * @param target
         */
        show: function (target) {
            if (Plugin.isShown()) {
                // 如果当前是显示状态，跳过
                return;
            }
            // 执行显示前回调
            Plugin.eventTrigger('beforeShow');
            // 设置状态为显示
            Plugin.togglerClass(target, 'show');

            // off-canvas panel
            $body.addClass(the.classShown);
            $element.addClass(the.classShown);
            // 将状态属性设置为显示
            the.state = 'shown';

            // 是否需要灰色遮罩层
            if (the.options.overlay) {
                // 插入遮罩div
                the.overlay = $('<div/>').prependTo($body);
                the.overlay.addClass(the.classOverlay);
                the.overlay.click(function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                    Plugin.hide(target);
                });
            }
            // 执行显示后回调
            Plugin.eventTrigger('afterShow');
        },

        hide: function (target) {
            if (!Plugin.isShown()) {
                return;
            }

            // 执行隐藏前回调
            Plugin.eventTrigger('beforeHide');
            // 设置状态为隐藏
            Plugin.togglerClass(target, 'hide');

            $body.removeClass(the.classShown);
            $element.removeClass(the.classShown);
            the.state = 'hidden';

            if (the.options.overlay && the.overlay) {
                the.overlay.remove();
            }

            Plugin.eventTrigger('afterHide');
        },

        /**
         * 更改显示&隐藏class
         *
         * @param target {object} 目标
         * @param mode {string} 要切换成的模式
         */
        togglerClass: function (target, mode) {
            let id = $(target).attr('id');
            let toggleBy;

            if (the.options.toggleBy && the.options.toggleBy[0] && the.options.toggleBy[0].target) {
                for (let i in the.options.toggleBy) {
                    if (the.options.toggleBy[i].target === id) {
                        toggleBy = the.options.toggleBy[i];
                    }
                }
            } else if (the.options.toggleBy && the.options.toggleBy.target) {
                toggleBy = the.options.toggleBy;
            }

            if (toggleBy) {
                let el = $(toggleBy.target);

                if (mode === 'show') {
                    el.addClass(toggleBy.state);
                }

                if (mode === 'hide') {
                    el.removeClass(toggleBy.state);
                }
            }
        },

        /**
         * 触发事件
         *
         * @param name {string} 事件名称
         * @param args {object} 参数
         */
        eventTrigger: function (name, args) {
            for (let i = 0; i < the.events.length; i++) {
                let event = the.events[i];
                if (event.name === name) {
                    if (event.one) {
                        // 一次性事件
                        if (!event.fired) {
                            // 设置事件属性为已触发，下次不执行
                            the.events[i].fired = true;
                            event.handler.call(this, the, args);
                        }
                    } else {
                        event.handler.call(this, the, args);
                    }
                }
            }
        },

        /**
         * 添加事件
         *
         * @param name {string} 事件名称
         * @param handler {function} 回调
         * @param one {boolean} 是否一次性
         */
        addEvent: function (name, handler, one) {
            the.events.push({
                name: name,
                handler: handler,
                one: one,
                fired: false
            });
        }
    };

    // 公开方法
    /**
     * 设置默认option
     *
     * @param options {object} 选项
     */
    the.setDefaults = function (options) {
        defaultOptions = options;
    };
    /**
     * 当前是否显示
     *
     * @returns {*|boolean}
     */
    the.isShown = function () {
        return Plugin.isShown();
    };
    /**
     * 隐藏
     *
     * @returns {*|undefined}
     */
    the.hide = function () {
        return Plugin.hide();
    };
    /**
     * 显示
     * @returns {*|undefined}
     */
    the.show = function () {
        return Plugin.show();
    };
    /**
     * 绑定事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.on = function (name, handler) {
        return Plugin.addEvent(name, handler);
    };
    /**
     * 绑定一次性事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.one = function (name, handler) {
        return Plugin.addEvent(name, handler, true);
    };

    Plugin.construct.apply(the, [options]);
    return the;
};
"use strict";

let EQuickSearch = function () {
    let target;
    let form;
    let input;
    let closeIcon;
    let resultWrapper;
    let resultDropdown;
    let resultDropdownToggle;
    let inputGroup;
    let query = '';

    let hasResult = false;
    let timeout = false;
    let isProcessing = false;
    let requestTimeout = 200; // ajax request fire timeout in milliseconds
    let spinnerClass = 'e-spinner e-spinner--input e-spinner--sm e-spinner--brand e-spinner--right';
    let resultClass = 'e-quick-search--has-result';
    let minLength = 2;
    /**
     * 显示等待状态
     */
    let showProgress = function () {
        isProcessing = true;
        inputGroup.addClass(spinnerClass);

        if (closeIcon) {
            closeIcon.hide();
        }
    };
    /**
     * 隐藏等待状态
     */
    let hideProgress = function () {
        isProcessing = false;
        inputGroup.removeClass(spinnerClass);

        if (closeIcon) {
            if (input.val().length < minLength) {
                closeIcon.hide();
            } else {
                closeIcon.css('display', 'flex');
            }
        }
    };
    /**
     * 显示搜索
     */
    let showDropdown = function () {
        if (resultDropdownToggle && !resultDropdown.hasClass('show')) {
            $(resultDropdownToggle).dropdown('toggle');
            $(resultDropdownToggle).dropdown('update');
        }
    };
    /**
     * 隐藏搜索
     */
    let hideDropdown = function () {
        if (resultDropdownToggle && resultDropdown.hasClass( 'show')) {
            $(resultDropdownToggle).dropdown('toggle');
        }
    };
    /**
     * 执行搜索
     */
    let processSearch = function () {
        // 如果有结果并且查询条件=当前输入条件
        if (hasResult && query === input.value) {
            hideProgress();
            target.addClass(resultClass);
            showDropdown();
            EUtil.scrollUpdate(resultWrapper[0]);
            return;
        }
        // 查询条件
        query = input.val();

        target.removeClass(resultClass);
        showProgress();
        // 这里暂时只对菜单进行搜索
        let result = searchMenu(query);
        resultWrapper.empty();
        if (result.length > 0) {
            hasResult = true;
            // 加载数据到搜索结果中
            $(result).each(function (index, menu) {
                resultWrapper.append(
                    '<a href="' + basePath + menu.url + '" data-title="' + menu.relName + '" class="e-notification-item e-tab-page-link">\
                        <div class="e-notification-item-icon">' + menu.icon + '</div>\
                        <div class="e-notification-item-details">\
                            <div class="e-notification-item-title">' + menu.name + '</div>\
                        </div>\
                    </a>'
                );
            });
        } else {
            hasResult = false;
            resultWrapper.html(
                '<div class="e-grid e-grid--ver" style="min-height: 200px;">\
                    <div class="e-grid e-grid--hor e-grid-item e-grid-item--fluid e-grid-item--middle">\
                        <div class="e-grid-item e-grid-item--middle e-align-center">\
                            暂无数据\
                        </div>\
                    </div>\
                </div>'
            );
        }
        hideProgress();
        target.addClass(resultClass);
        showDropdown();
        EUtil.scrollUpdate(resultWrapper[0]);
    };
    /**
     * 根据关键字搜索菜单
     *
     * @param query {string} 关键字
     * @return {Array} 菜单列表
     */
    let searchMenu = function (query) {
        let result = [];
        if(EUtil.isNotBlank(query)){
            // query = query.toUpperCase();
            let menus = ETool.getUser(true).menus;
            $(menus).each(function (index, menu) {
                if (EUtil.isNotBlank(menu.url) && menu.name.indexOf(query) > -1) {
                    result.push({
                        name: menu.name.replace(new RegExp(query, 'g'), '<span class="e-font-danger">' + query + '</span>'),
                        relName: menu.name,
                        icon: menu.icon,
                        url: menu.url
                    })
                }
            });
        }
        return result;
    };
    /**
     * 取消
     *
     * @param e
     */
    let handleCancel = function (e) {
        input.val('');
        query = '';
        hasResult = false;
        closeIcon.hide();
        target.removeClass(resultClass);
        hideDropdown();
    };

    let handleSearch = function () {
        if (input.val().length < minLength) {
            hideProgress();
            hideDropdown();
            return;
        }

        if (isProcessing === true) {
            return;
        }

        if (timeout) {
            clearTimeout(timeout);
        }

        timeout = setTimeout(function () {
            processSearch();
        }, requestTimeout);
    };

    return {
        init: function ($element) {
            target = $element;
            // Init
            form = $element.find('.e-quick-search-form');
            input = $element.find('.e-quick-search-input');
            closeIcon = $element.find('.e-quick-search-close');
            resultDropdown = $element.find('.dropdown-menu');
            resultDropdownToggle = $element.find('[data-toggle="dropdown"]');
            inputGroup = $element.find('.input-group');
            resultWrapper = $(target).find('.e-quick-search-wrapper');

            input.on('keyup', handleSearch);
            input.on('focus', handleSearch);

            // 按回车
            form.on('keypress', function (e) {
                let key = e.charCode || e.keyCode || 0;
                if (key === 13) {
                    e.preventDefault();
                }
            });

            closeIcon.on('click', handleCancel);

            // 打开搜索将焦点放到input上
            let $toggle = $('#quick_search_toggle');
            if ($toggle.length) {
                $toggle.on('shown.bs.dropdown', function () {
                    input.focus();
                });
            }
        }
    };
};

$(document).ready(function () {
    let $quickSearch = $('#quick_search_inline');
    if ($quickSearch.length) {
        EQuickSearch().init($quickSearch);
    }
});
/**
 * @class ETabPage 标签页
 */
let ETabPage = function (selector, options) {
    let util = EUtil;

    let the = this;
    let element = $(selector);
    if (element.length > 0) {
        return;
    }

    /**
     * 默认设置
     * @type {{}}
     */
    let defaultOptions = {
        tabNameLength: 10,
        toLeft: $('.to-left'),
        toRight: $('.to-right'),
        tool: {
            refreshPage: $('.refresh-page'),
            closeOther: $('.close-other'),
            closeAll: $('.close-all')
        },
        containerScroll: $('.container-scroll'),
        conTabs: $('.con-tabs'),
        pageContainer: $('.page-container'),
        tabPage: $('.tab-page')
    };

    /**
     * 私有方法
     */
    let Plugin = {
        index: 1,
        /**
         * Construct
         */
        construct: function (options) {
            // 初始化
            Plugin.init(options);
            // 绑定事件
            Plugin.build();
            return the;
        },
        build: function () {
            // 点击向左按钮
            defaultOptions.toLeft.click(function () {
                let marginLeft = Number(defaultOptions.conTabs.css('margin-left').replace('px', ''));
                if (marginLeft !== 0) {
                    let tabsScrollWidth = Plugin.calcWidth(defaultOptions.containerScroll); // tab 工具条可视宽度
                    let targetMarginLeft = marginLeft + (tabsScrollWidth * 0.8);
                    if (targetMarginLeft > 0) {
                        targetMarginLeft = 0;
                    }
                    defaultOptions.conTabs.animate({marginLeft: targetMarginLeft + 'px'}, 'fast');
                }
            });
            // 点击向右按钮
            defaultOptions.toRight.click(function () {
                let marginLeft = Number(defaultOptions.conTabs.css('margin-left').replace('px', ''));
                let tabsScrollWidth = Plugin.calcWidth(defaultOptions.containerScroll); // tab 工具条可视宽度
                let conTabsWidth = Plugin.calcWidth(defaultOptions.conTabs.children()); // tab 总宽
                let targetMarginLeft = marginLeft - (tabsScrollWidth * 0.8);

                if (Math.abs(targetMarginLeft) > (conTabsWidth - tabsScrollWidth)) {
                    targetMarginLeft = (conTabsWidth - tabsScrollWidth) * -1;
                }
                if (targetMarginLeft > 0) {
                    targetMarginLeft = 0;
                }
                defaultOptions.conTabs.animate({marginLeft: targetMarginLeft + 'px'}, 'fast');
            });
            // 刷新按钮
            defaultOptions.tool.refreshPage.click(function () {
                Plugin.refreshActivePage();
            });
            // 关闭全部 (只关闭允许关闭的tab)
            defaultOptions.tool.closeAll.click(function () {
                let retainTab = null;
                defaultOptions.conTabs.children().each(function () {
                    let $this = $(this);
                    let $tabClose = $this.find('.tab-close');
                    if ($tabClose != null && $tabClose.length > 0) {
                        Plugin.closeTab($this);
                    } else {
                        retainTab = $this;
                    }
                });
                if (retainTab != null) {
                    Plugin.setTabState(retainTab.find('a.btn'), true);
                }
                Plugin.toActiveTab();
            });
            // 关闭其他
            defaultOptions.tool.closeOther.click(function () {
                defaultOptions.conTabs.children().each(function () {
                    let $this = $(this);
                    let $tabClose = $this.find('.tab-close');
                    if ($tabClose != null && $tabClose.length > 0 && !$this.hasClass('active')) {
                        Plugin.closeTab($this);
                    }
                });
                Plugin.toActiveTab();
            });
        },
        /**
         * 初始化
         */
        init: function () {
            Plugin.bindClickTab(); // 绑定点击tab事件
        },

        /**
         * 关闭tab事件
         */
        bindClose: function () {
            defaultOptions.conTabs.find('.tab-close').unbind('click').click(function (e) {
                e.preventDefault();
                Plugin.closeTab($(this).parents('li'));
            });
        },
        /**
         * 点击tab事件
         */
        bindClickTab: function () {
            let alreadyClickedTimeout;
            defaultOptions.conTabs.on('click', 'a.btn', function () {
                let $tab = $(this);
                if ($tab.data('alreadyClicked')) {
                    // 双击
                    console.log('dblclick');
                    $tab.data('alreadyClicked', false); // 重置点击状态
                    clearTimeout(alreadyClickedTimeout); // 阻止单击事件执行

                    if ($tab.parent().hasClass('active')) { // 如果用户双击已激活tab,刷新页面
                        Plugin.refreshActivePage();
                    }
                } else {
                    $tab.data('alreadyClicked', true);
                    alreadyClickedTimeout = setTimeout(function () {
                        $tab.data('alreadyClicked', false); // 重置点击状态
                        // 单击
                        console.log('click');
                        if (!$tab.parent().hasClass('active')) {
                            $tab.data('alreadyClicked', false); // 重置点击状态
                            defaultOptions.conTabs.children('.active').removeClass('active');
                            defaultOptions.pageContainer.children('.active').removeClass('active');
                            Plugin.setTabState($tab, true);
                            Plugin.toActiveTab();
                            Plugin.customCallback();
                        }
                    }, 200); // 点击事件间隔300毫秒以内会被认为是双击
                }
                return false;
            });
        },

        /**
         * 刷新当前激活标签页
         */
        refreshActivePage: function (url) {
            if (EUtil.isBlank(url)) {
                url = defaultOptions.conTabs.children('.active').find('a.btn').attr('data-url');
            }
            let $iframe = defaultOptions.pageContainer.find('iframe.page-frame[src="' + url + '"]');
            if ($iframe.length) {
                $iframe.attr('src', url);
            }
        },
        /**
         * 显示指定tab内容
         *
         * @param $tab {jquery element} (a.btn)
         * @param isShow {boolean} 是否显示
         */
        setTabState: function ($tab, isShow) {
            let url = $tab.attr('data-url');
            if (isShow) {
                defaultOptions.pageContainer.find('iframe.page-frame[src="' + url + '"]').addClass('active');
                $tab.parent().addClass('active');
            } else {
                defaultOptions.pageContainer.find('iframe.page-frame[src="' + url + '"]').removeClass('active');
                $tab.parent().removeClass('active');
            }
        },
        /**
         * 关闭标签页
         * @param $tab {jquery element} 标签页 (li)
         */
        closeTab: function ($tab) {
            let url = $tab.find('a').data('url');
            // 来源页面pathname，用于关闭标签页后返回来源页面
            let sourcePagePathname =  $tab.find('a').data('source');
            let nextTab;
            if ($tab.hasClass('active')) { // 如果关闭的是当前激活tab,自动切换到前/后的标签
                if(EUtil.isNotBlank(sourcePagePathname)){
                    let $sourceTabA = defaultOptions.conTabs.find('[data-url="' + sourcePagePathname + '"]');
                    if($sourceTabA.length){
                        nextTab = $sourceTabA.parent();
                    }
                }
                if(nextTab == null){
                    nextTab = $tab.prev();
                    if (nextTab == null || nextTab.length === 0) {
                        nextTab = $tab.next();
                    }
                }
            }
            $tab.remove();
            defaultOptions.pageContainer.find('iframe.page-frame[src="' + url + '"]').remove();
            if (nextTab != null) {
                Plugin.setTabState(nextTab.find('a.btn'), true);
                Plugin.toActiveTab();
                Plugin.customCallback();
            }
        },
        /**
         * 关闭指定url页面
         * @param url {string} 页面url
         */
        closeTabByUrl: function (url) {
            let a = defaultOptions.conTabs.find('a.btn[data-url="' + url + '"]');
            if (a != null && a.length > 0) {
                Plugin.closeTab(a.parent());
            } else {
                console.warn('关闭页面[' + url + ']失败,url未在应用中打开');
            }
        },
        /**
         * 添加Tab页
         * @param name {string} 标签页名称
         * @param url {string} 地址
         * @param canClose {boolean} 是否可以关闭
         * @param sourcePagePathname {string} 来源页面pathname，用于关闭标签页后返回来源页面
         */
        addTab: function (name, url, canClose, sourcePagePathname) {
            if (util.isBlank(url)) return;
            if (Plugin.needOpen(url)) {
                Plugin.index++;
                name = name.trim();
                let _tab = '\
                    <li class="active">\
                        <a class="btn btn-default m-btn--icon tab" href="#" data-source="' + sourcePagePathname + '" data-url="' + url + '" title="' + name + '" data-target="iframe-' + Plugin.index + '" data-alreadyClicked="false">\
                            <span>' + Plugin.getTabName(name) + ((typeof canClose === 'undefined' || canClose) ? '<i class="tab-close la la-close"></i>' : '') + '</span>' +
                    '</a>' +
                    '</li>';
                let _iframe = '<iframe src="' + url + '" data-source="' + sourcePagePathname + '" frameborder="0" name="iframe-' + Plugin.index + '" class="page-frame active animation-fade"></iframe>';
                defaultOptions.conTabs.children('.active').removeClass('active');
                defaultOptions.pageContainer.children('.active').removeClass('active');
                defaultOptions.conTabs.append(_tab);
                defaultOptions.pageContainer.append(_iframe);
                defaultOptions.conTabs.width(Plugin.calcWidth(defaultOptions.conTabs.children()) + 30);
                Plugin.bindClose(); // 绑定点击关闭事件
            } else {
                Plugin.setTabState(defaultOptions.conTabs.find('.active').find('a.btn'), false);
                Plugin.setTabState(defaultOptions.conTabs.find('[data-url="' + url + '"]'), true);
                Plugin.customCallback();
            }
            Plugin.toActiveTab();
        },
        /**
         * 执行自定义回调
         */
        customCallback: function () {
            let iframe = defaultOptions.pageContainer.children('.active');
            if (iframe.length > 0) {
                if (typeof iframe[0].contentWindow.ETab !== 'undefined') {
                    // 检查是否需要刷新页面
                    if (util.isFunction(iframe[0].contentWindow.ETab.needRefresh) && iframe[0].contentWindow.ETab.needRefresh()) {
                        Plugin.refreshActivePage();
                    }
                    // 检查是否需要提交表单
                    if (util.isFunction(iframe[0].contentWindow.ETab.needSubmitForm) && iframe[0].contentWindow.ETab.needSubmitForm()) {
                        let $btn = $(iframe[0].contentDocument).find('.btn.btn-search');
                        if ($btn.length > 0) {
                            $btn.click();
                        }
                    }
                }
            }
        },
        /**
         * 跳转到当前激活
         */
        toActiveTab: function () {
            let $activeTab = defaultOptions.conTabs.find('.active');
            let activeWidth = Plugin.calcWidth($activeTab); // 当前激活tab宽度
            let prevAllWidth = Plugin.calcWidth($activeTab.prevAll()); // 当前激活tab前面tabs总宽
            let tabsScrollWidth = Plugin.calcWidth(defaultOptions.containerScroll); // tab 工具条可视宽度
            let conTabsWidth = Plugin.calcWidth(defaultOptions.conTabs.children()); // tab 总宽
            let marginLeft = Number(defaultOptions.conTabs.css('margin-left').replace('px', ''));
            if (conTabsWidth > tabsScrollWidth) {
                if (Math.abs(marginLeft)> prevAllWidth) { // 目标标签页隐藏在左侧
                    marginLeft = prevAllWidth * -1;
                } else if ((Math.abs(marginLeft) + tabsScrollWidth) < (prevAllWidth + activeWidth)) { // 目标标签页隐藏在右侧
                    marginLeft = (prevAllWidth + activeWidth - tabsScrollWidth) * -1;
                }
            } else {
                marginLeft = 0;
            }
            defaultOptions.conTabs.animate({marginLeft: marginLeft + 'px'}, 'fast');
        },
        /**
         * 计算总宽度
         * @param $tabs
         * @returns {number}
         */
        calcWidth: function ($tabs) {
            if ($tabs != null && $tabs.length > 0) {
                let width = 0;
                $($tabs).each(function () {
                    width += $(this).outerWidth(true);
                });
                return Math.ceil(width);
            }
            return 0;
        },
        /**
         * 获取标签页名称
         * @param name
         * @returns {string}
         */
        getTabName: function (name) {
            if (util.isNotBlank(name)) {
                return util.subStr(name, defaultOptions.tabNameLength);
            } else {
                return '新建标签页';
            }
        },
        /**
         * 检查url是否需要打开
         *
         * @param url 链接地址
         * @returns {boolean}
         */
        needOpen: function (url) {
            if (util.isNotBlank(url)) {
                let $tab = defaultOptions.conTabs.find('[data-url="' + url + '"]');
                return $tab.length === 0;
            }
            return false;
        },
    };

    //////////////////////////
    // **     开放方法    ** //
    //////////////////////////
    /**
     * 添加Tab页
     * @param name {string} 标签页名称
     * @param url {string} 页面url
     * @param canClose {boolean} 是否可以关闭
     * @param sourcePagePathname {string} 来源页面pathname，用于关闭标签页后返回来源页面
     */
    the.addTab = function (name, url, canClose, sourcePagePathname) {
        return Plugin.addTab(name, url, canClose, sourcePagePathname);
    };
    /**
     * 关闭指定url页面
     *
     * @param url {string} 页面url
     */
    the.closeTabByUrl = function (url) {
        return Plugin.closeTabByUrl(url);
    };
    /**
     * 刷新指定页面
     *
     * @param url {string} 页面url
     */
    the.refresh = function (url) {
        Plugin.refreshActivePage(url);
    };
    /**
     * 跳转到当前激活
     */
    the.toActiveTab = function(){
        Plugin.toActiveTab();
    };
    //== Construct plugin
    Plugin.construct.apply(the, [options]);

    return the;
};
/**
 * tab callback
 * @type {{needRefresh: (function(): boolean), needSubmitForm: (function(): boolean)}}
 */
window.ETab = {
    /**
     * 激活当前tab是否需要刷新当前页面
     *
     * @returns {boolean}
     */
    needRefresh: function () {
        return false;
    },
    /**
     * 激活当前tab是否需要提交查询表单
     *
     * @returns {boolean}
     */
    needSubmitForm: function () {
        return false;
    }
};
"use strict";
// 向导表单
let EWizard = function (selector, options) {
    // 目标元素
    let the = this;

    // 查找对象
    let $element = $(selector);

    if (!$element.length) {
        return;
    }

    // 默认设置
    let defaultOptions = {
        startStep: 1
    };

    // 私有方法
    let Plugin = {
        construct: function (options) {
            if (EUtil.data($element).has('sample')) {
                // 如果已经初始化，直接从缓存获取对象
                the = EUtil.data($element).get('sample');
            } else {
                // 初始化插件
                Plugin.init(options);
                // 绑定事件
                Plugin.build();
                EUtil.data($element).set('sample', the);
            }
            return the;
        },

        /**
         * 初始化
         *
         * @param options {object} option
         */
        init: function (options) {
            // 事件
            the.events = [];

            // handlers
            the.eventHandlers = {};

            // 合并默认选项&传入option
            the.options = $.extend(true, {}, defaultOptions, options);

            // 操作按钮
            the.btn = {
                next: $element.find('.wizard-btn-next'),
                prev: $element.find('.wizard-btn-prev'),
                submit: $element.find('.wizard-btn-submit')
            };
            // 向导内容
            the.steps = $element.find('.e-wizard-step');
            the.navs = $element.find('.e-wizard-nav-item');

            // 当前 step
            the.currentStep = 1;
            // 共计 step
            the.totalSteps = the.steps.length;
            the.stoped = false;

            if (the.options.startStep > 1) {
                Plugin.goToStep(the.options.startStep);
            } else {
                Plugin.updateUI();
            }
        },
        /**
         * 绑定事件
         */
        build: function () {
            the.btn.next.click(Plugin.goNext);
            the.btn.prev.click(Plugin.goPrev);
            // 点击导航
            the.navs.click(function () {
                let clickStep = $(this).index() + 1;
                if (clickStep !== the.currentStep) {
                    Plugin.goToStep(clickStep);
                }
            });
        },
        /**
         * 停止
         */
        stop: function () {
            the.stopped = true;
        },

        /**
         * 开始
         */
        start: function () {
            the.stopped = false;
        },
        /**
         * 转到上一步
         */
        goPrev: function () {
            return Plugin.goToStep(Plugin.getPrevStep());
        },

        /**
         * 转到下一步
         */
        goNext: function () {
            return Plugin.goToStep(Plugin.getNextStep());
        },

        /**
         * 转到最后一步
         */
        goLast: function () {
            return Plugin.goToStep(the.totalSteps);
        },

        /**
         * 转到第一步
         */
        goFirst: function () {
            return Plugin.goToStep(1);
        },

        /**
         * 获取上一步 step
         */
        getPrevStep: function () {
            if ((the.currentStep - 1) >= 1) {
                return the.currentStep - 1;
            } else {
                return 1;
            }
        },

        /**
         * 获取下一步 step
         */
        getNextStep: function () {
            if (the.totalSteps >= (the.currentStep + 1)) {
                return the.currentStep + 1;
            } else {
                return the.totalSteps;
            }
        },

        /**
         * 转到指定的step
         *
         * @param stepIndex {number} step index （从1开始）
         * @return {*}
         */
        goToStep: function (stepIndex) {
            if (the.stoped) {
                // 被停止
                return;
            }

            if (!stepIndex || stepIndex === the.currentStep || stepIndex > the.totalSteps) {
                return;
            }

            let callback = null;
            let currentStep = the.steps[the.currentStep - 1];

            // 这里验证是否调用过回调，以免用户在beforeNext/beforePrev中调用wizard.goToStep导致死循环
            if(!$element.hasClass('already-called')){
                $element.addClass('already-called');
                // 调用触发事件
                if (stepIndex < the.currentStep) {
                    // 上一步
                    callback = Plugin.eventTrigger('beforePrev', currentStep);
                } else {
                    callback = Plugin.eventTrigger('beforeNext', currentStep);
                }
            }

            if (callback !== false) {
                Plugin.eventTrigger('beforeChange', currentStep);

                the.currentStep = stepIndex;

                Plugin.updateUI();

                Plugin.eventTrigger('change', currentStep);
            }
            if (stepIndex < the.currentStep) {
                // 上一步
                Plugin.eventTrigger('afterPrev', currentStep);
            } else {
                Plugin.eventTrigger('afterNext', currentStep);
            }
            $element.removeClass('already-called');
            return the;
        },
        /**
         * 更改 ui
         */
        updateUI: function () {
            $element.removeClass('already-called');

            let wizardStatus = null;

            if (Plugin.isFirstStep()) {
                wizardStatus = 'first'
            } else if (Plugin.isLastStep()) {
                wizardStatus = 'last'
            } else {
                wizardStatus = 'between'
            }
            // 设置向导状态
            $element.attr('data-wizard-status', wizardStatus);

            let length = the.totalSteps;
            let currentIndex = the.currentStep - 1;
            while (length--) {
                let stepStatus = null;
                if (length === currentIndex) {
                    // 当前激活
                    stepStatus = 'current';
                } else {
                    if (length < currentIndex) {
                        // 已填
                        stepStatus = 'done';
                    } else {
                        stepStatus = 'pending';
                    }
                }
                $(the.steps[length]).attr('data-step-status', stepStatus);
                $(the.navs[length]).attr('data-step-status', stepStatus);
            }
        },

        /**
         * 是否为第一步
         *
         * @returns {boolean} true/false
         */
        isFirstStep: function () {
            return the.currentStep === 1;
        },

        /**
         * 是否为中间步数
         *
         * @returns {boolean} true/false
         */
        isBetweenStep: function () {
            return Plugin.isLastStep() === false && Plugin.isFirstStep() === false;
        },
        /**
         * 是否为最后一步
         *
         * @returns {boolean} true/false
         */
        isLastStep: function () {
            return the.currentStep === the.totalSteps;
        },

        /**
         * 触发事件
         *
         * @param name {string} 事件名称
         * @param args {object} 参数
         * @return {*} 如果有多个事件，以最后一个事件返回值为准
         */
        eventTrigger: function (name, args) {
            let result = null;
            for (let i = 0; i < the.events.length; i++) {
                let event = the.events[i];
                if (event.name === name) {
                    if (event.one) {
                        // 一次性事件
                        if (!event.fired) {
                            // 设置事件属性为已触发，下次不执行
                            the.events[i].fired = true;
                            result = event.handler.call(this, the, args);
                        }
                    } else {
                        result = event.handler.call(this, the, args);
                    }
                }
            }
            return result;
        },

        /**
         * 添加事件
         *
         * @param name {string} 事件名称
         * @param handler {function} 回调
         * @param one {boolean} 是否一次性
         */
        addEvent: function (name, handler, one) {
            the.events.push({
                name: name,
                handler: handler,
                one: one,
                fired: false
            });
        },
        /**
         * 移除事件
         *
         * @param name {string} 事件名称
         */
        removeEvent: function (name) {
            if (the.events[name]) {
                delete the.events[name];
            }
        }
    };

    // 公开方法

    /**
     * 设置默认option
     *
     * @param options {object} 选项
     */
    the.setDefaults = function (options) {
        defaultOptions = options;
    };

    /**
     * 转到下一步
     */
    the.goNext = function () {
        return Plugin.goNext();
    };

    /**
     * 转到上一步
     */
    the.goPrev = function () {
        return Plugin.goPrev();
    };

    /**
     * 转到最后一步
     */
    the.goLast = function () {
        return Plugin.goLast();
    };

    /**
     * 停止
     */
    the.stop = function () {
        return Plugin.stop();
    };

    /**
     * 开始
     */
    the.start = function () {
        return Plugin.start();
    };

    /**
     * 转到第一步
     */
    the.goFirst = function () {
        return Plugin.goFirst();
    };

    /**
     * 转到指定的step
     *
     * @param stepIndex {number} step index
     * @return {*}
     */
    the.goToStep = function (stepIndex) {
        return Plugin.goToStep(stepIndex);
    };

    /**
     * 获取当前步数
     *
     * @returns {number} step
     */
    the.getStep = function () {
        return the.currentStep;
    };

    /**
     * 是否为最后一步
     *
     * @returns {boolean} true/false
     */
    the.isLastStep = function () {
        return Plugin.isLastStep();
    };

    /**
     * 是否为第一步
     *
     * @returns {boolean} true/false
     */
    the.isFirstStep = function () {
        return Plugin.isFirstStep();
    };

    /**
     * 绑定事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.on = function (name, handler) {
        return Plugin.addEvent(name, handler, false);
    };
    /**
     * 绑定一次性事件
     *
     * @param name {string} 事件名称
     * @param handler {function} 回调
     * @returns {*|void}
     */
    the.one = function (name, handler) {
        return Plugin.addEvent(name, handler, true);
    };

    Plugin.construct.apply(the, [options]);
    return the;
};
'use strict';
(function ($) {

    let pluginName = 'EDataTable';
    let pfx = 'e-';
    let util = EUtil;
    let app = EApp;
    let tool = ETool;

    // 插件设置
    $.fn[pluginName] = function (options) {
        // 全局
        let datatable = this;

        // 开启调试?
        // 1) 每次刷新时将清除状态
        // 2) 输出一些日志
        datatable.debug = false;

        datatable.API = {
            record: null,
            value: null,
            params: null
        };

        datatable.scrollbarElement = null;

        let Plugin = {
            /********************
             ** 私有
             ********************/
            // 是否初始化
            isInit: false,
            // cell
            cellOffset: 110,
            // 图标
            iconOffset: 15,
            // 状态id
            stateId: 'meta',
            // ajax参数
            ajaxParams: {},
            // 分页
            pagingObject: {},

            init: function (options) {
                // 是否普通表格
                let isHtmlTable = false;
                // 数据源选项空
                if (options.data.source === null) {
                    // 当做普通表格处理
                    Plugin.extractTable();
                    isHtmlTable = true;
                }

                Plugin.setupBaseDOM.call();
                Plugin.setupDOM(datatable.table);
                Plugin.spinnerCallback(true);

                // 设置查询条件
                Plugin.setDataSourceQuery(Plugin.getOption('data.source.read.params.query'));

                // 渲染后事件
                $(datatable).on(pfx + 'datatable--on-layout-updated', Plugin.afterRender);

                // 如果是调试模式,移除状态
                if (datatable.debug) Plugin.stateRemove(Plugin.stateId);

                // 初始化拓展方法
                $.each(Plugin.getOption('extensions'), function (extName, extOptions) {
                    if (typeof $.fn[pluginName][extName] === 'function')
                        new $.fn[pluginName][extName](datatable, extOptions);
                });

                // 获取数据
                if (options.data.type === 'remote' || options.data.type === 'local') {
                    if (options.data.saveState === false
                        || options.data.saveState.cookie === false
                        && options.data.saveState.webstorage === false) {
                        Plugin.stateRemove(Plugin.stateId);
                    }
                    // 如果数据在本地并且指定数据
                    if (options.data.type === 'local' && typeof options.data.source === 'object') {
                        datatable.dataSet = datatable.originalDataSet = Plugin.dataMapCallback(options.data.source);
                    }
                    Plugin.dataRender();
                }

                // 如果是普通表格,移除head/foot并重新设置
                if (isHtmlTable) {
                    $(datatable.tableHead).find('tr').remove();
                    $(datatable.tableFoot).find('tr').remove();
                }
                // 设置head
                Plugin.setHeadTitle();
                // 设置foot
                if (Plugin.getOption('layout.footer')) {
                    Plugin.setHeadTitle(datatable.tableFoot);
                }

                // 如果未设置header则删除thead
                if (typeof options.layout.header !== 'undefined' &&
                    options.layout.header === false) {
                    $(datatable.table).find('thead').remove();
                }

                // 如果未设置footer则删除tfoot
                if (typeof options.layout.footer !== 'undefined' &&
                    options.layout.footer === false) {
                    $(datatable.table).find('tfoot').remove();
                }

                // 如果数据在本地,更新布局
                if (options.data.type === null ||
                    options.data.type === 'local') {
                    Plugin.setupCellField.call();
                    Plugin.setupTemplateCell.call();

                    // setup nested datatable, if option enabled
                    Plugin.setupSubDatatable.call();

                    // setup extra system column properties
                    Plugin.setupSystemColumn.call();
                    Plugin.redraw();
                }

                let width;
                let initialWidth = false;
                $(window).resize(function () {
                    // 获取初始宽度
                    if (!initialWidth) {
                        width = $(this).width();
                        initialWidth = true;
                    }
                    // 仅在浏览器窗口宽度改变时重选渲染
                    if ($(this).width() !== width) {
                        width = $(this).width();
                        Plugin.fullRender();
                    }
                });
                // 清空设置的高度
                $(datatable).height('');

                // search按回车时搜索
                $(Plugin.getOption('search.input')).on('keyup', function (e) {
                    if (Plugin.getOption('search.onEnter') && e.which !== 13) return;
                    Plugin.search($(this).val());
                });

                return datatable;
            },

            /**
             * 提取静态table内容放入dataSource
             */
            extractTable: function () {
                let columns = [];
                let headers = $(datatable).find('tr:first-child th').get().map(function (cell, i) {
                    let field = $(cell).data('field');
                    if (typeof field === 'undefined') {
                        field = $(cell).text().trim();
                    }
                    let column = {field: field, title: field};
                    for (let ii in options.columns) {
                        if (options.columns[ii].field === field) {
                            column = $.extend(true, {}, options.columns[ii], column);
                        }
                    }
                    columns.push(column);
                    return field;
                });
                // 自动创建 columns config
                options.columns = columns;

                let rowProp = [];
                let source = [];

                $(datatable).find('tr').each(function () {
                    if ($(this).find('td').length) {
                        rowProp.push($(this).prop('attributes'));
                    }
                    let td = {};
                    $(this).find('td').each(function (i, cell) {
                        td[headers[i]] = cell.innerHTML.trim();
                    });
                    if (!util.isEmpty(td)) {
                        source.push(td);
                    }
                });

                options.data.attr.rowProps = rowProp;
                options.data.source = source;
            },

            /**
             * 更新布局
             */
            layoutUpdate: function () {

                // setup nested datatable, if option enabled
                Plugin.setupSubDatatable.call();

                // setup extra system column properties
                Plugin.setupSystemColumn.call();

                // 设置 hover event
                Plugin.setupHover.call();

                if (typeof options.detail === 'undefined'
                    // temporary disable lock column in subtable
                    && Plugin.getDepth() === 1) {
                    // 锁定列
                    Plugin.lockTable.call();
                }

                Plugin.columnHide.call();

                Plugin.resetScroll();

                // 如果不是已锁定的列
                if (!Plugin.isLocked()) {
                    Plugin.redraw.call();
                    // check if its not a subtable and has autoHide option enabled
                    if (Plugin.getOption('rows.autoHide') === true) {
                        Plugin.autoHide();
                    }
                    // reset row
                    $(datatable.table).find('.' + pfx + 'datatable__row').css('height', '');
                }

                Plugin.rowEvenOdd.call();

                Plugin.sorting.call();

                Plugin.scrollbar.call();

                if (!Plugin.isInit) {
                    // run once dropdown inside datatable
                    Plugin.dropdownFix();
                    $(datatable).trigger(pfx + 'datatable--on-init', {
                        table: $(datatable.wrap).attr('id'),
                        options: options
                    });
                    Plugin.isInit = true;
                }

                $(datatable).trigger(pfx + 'datatable--on-layout-updated', {table: $(datatable.wrap).attr('id')});
                $('.tooltip').remove();
            },
            /**
             * 锁定表
             * @return {{init: init, lockEnabled: boolean, enable: enable}}
             */
            lockTable: function () {
                let lock = {
                    lockEnabled: false,
                    init: function () {
                        // check if table should be locked columns
                        lock.lockEnabled = Plugin.lockEnabledColumns();
                        if (lock.lockEnabled.left.length === 0 &&
                            lock.lockEnabled.right.length === 0) {
                            return;
                        }
                        lock.enable();
                    },
                    enable: function () {
                        let enableLock = function (tablePart) {
                            // 如果已经有锁定列
                            if ($(tablePart).find('.' + pfx + 'datatable__lock').length > 0) {
                                Plugin.log('Locked container already exist in: ', tablePart);
                                return;
                            }
                            // 如果为空
                            if ($(tablePart).find('.' + pfx + 'datatable__row').length === 0) {
                                Plugin.log('No row exist in: ', tablePart);
                                return;
                            }

                            // 锁定div容器
                            let lockLeft = $('<div/>').addClass(pfx + 'datatable__lock ' + pfx + 'datatable__lock--left');
                            let lockScroll = $('<div/>').addClass(pfx + 'datatable__lock ' + pfx + 'datatable__lock--scroll');
                            let lockRight = $('<div/>').addClass(pfx + 'datatable__lock ' + pfx + 'datatable__lock--right');

                            $(tablePart).find('.' + pfx + 'datatable__row').each(function () {
                                // 创建新row用于锁定列并设置数据
                                let rowLeft = $('<tr/>').addClass(pfx + 'datatable__row').data('obj', $(this).data('obj')).appendTo(lockLeft);
                                let rowScroll = $('<tr/>').addClass(pfx + 'datatable__row').data('obj', $(this).data('obj')).appendTo(lockScroll);
                                let rowRight = $('<tr/>').addClass(pfx + 'datatable__row').data('obj', $(this).data('obj')).appendTo(lockRight);
                                $(this).find('.' + pfx + 'datatable__cell').each(function () {
                                    let locked = $(this).data('locked');
                                    if (typeof locked !== 'undefined') {
                                        if (typeof locked.left !== 'undefined' || locked === true) {
                                            // 如果没设置锁定在左边还是右边,默认锁定在左边
                                            $(this).appendTo(rowLeft);
                                        }
                                        if (typeof locked.right !== 'undefined') {
                                            $(this).appendTo(rowRight);
                                        }
                                    } else {
                                        $(this).appendTo(rowScroll);
                                    }
                                });
                                rowLeft.attr('data-id', $(this).data('id'));
                                rowScroll.attr('data-id', $(this).data('id'));
                                rowRight.attr('data-id', $(this).data('id'));
                                // 移除旧row
                                $(this).remove();
                            });

                            if (lock.lockEnabled.left.length > 0) {
                                $(datatable.wrap).addClass(pfx + 'datatable--lock');
                                $(lockLeft).appendTo(tablePart);
                            }
                            if (lock.lockEnabled.left.length > 0 || lock.lockEnabled.right.length > 0) {
                                $(lockScroll).appendTo(tablePart);
                            }
                            if (lock.lockEnabled.right.length > 0) {
                                $(datatable.wrap).addClass(pfx + 'datatable--lock');
                                $(lockRight).appendTo(tablePart);
                            }
                        };

                        $(datatable.table).find('thead,tbody,tfoot').each(function () {
                            let tablePart = this;
                            if ($(this).find('.' + pfx + 'datatable__lock').length === 0) {
                                $(this).ready(function () {
                                    enableLock(tablePart);
                                });
                            }
                        });
                    }
                };
                lock.init();
                return lock;
            },

            /**
             * 调整大小后重新渲染
             */
            fullRender: function () {
                $(datatable.tableHead).empty();
                Plugin.setHeadTitle();
                if (Plugin.getOption('layout.footer')) {
                    $(datatable.tableFoot).empty();
                    Plugin.setHeadTitle(datatable.tableFoot);
                }

                Plugin.spinnerCallback(true);
                $(datatable.wrap).removeClass(pfx + 'datatable--loaded');

                Plugin.insertData();
            },
            /**
             * 获取锁定列
             * @return {{left: Array, right: Array}}
             */
            lockEnabledColumns: function () {
                let screen = $(window).width();
                let columns = options.columns;
                let enabled = {left: [], right: []};
                $.each(columns, function (i, column) {
                    if (typeof column.locked !== 'undefined') {
                        if (typeof column.locked.left !== 'undefined') {
                            if (util.getBreakpoint(column.locked.left) <= screen) {
                                enabled['left'].push(column.locked.left);
                            }
                        }
                        if (typeof column.locked.right !== 'undefined') {
                            if (util.getBreakpoint(column.locked.right) <= screen) {
                                enabled['right'].push(column.locked.right);
                            }
                        }
                    }
                });
                return enabled;
            },

            /**
             * 执行 render 事件后
             * '+pfx+'-datatable--on-layout-updated
             * @param e
             * @param args
             */
            afterRender: function (e, args) {
                $(datatable).ready(function () {
                    // 重绘表格的锁定列
                    if (Plugin.isLocked()) {
                        Plugin.redraw();
                    }

                    $(datatable.tableBody).css('visibility', '');
                    $(datatable.wrap).addClass(pfx + 'datatable--loaded');

                    Plugin.spinnerCallback(false);
                });
            },
            /**
             * 修复dropdown
             */
            dropdownFix: function () {
                let dropdownMenu;
                $('body').on('show.bs.dropdown', '.' + pfx + 'datatable .' + pfx + 'datatable__body', function (e) {
                    dropdownMenu = $(e.target).find('.dropdown-menu');
                    $('body').append(dropdownMenu.detach());
                    dropdownMenu.css('display', 'block');
                    dropdownMenu.position({
                        'my': 'right top',
                        'at': 'right bottom',
                        'of': $(e.relatedTarget)
                    });
                    // 如果表格在modal里面
                    if (datatable.closest('.modal').length) {
                        // 增加下拉的 z-index
                        dropdownMenu.css('z-index', '2000');
                    }
                }).on('hide.bs.dropdown', '.' + pfx + 'datatable .' + pfx + 'datatable__body', function (e) {
                    $(e.target).append(dropdownMenu.detach());
                    dropdownMenu.hide();
                });
            },

            hoverTimer: 0,
            isScrolling: false,

            setupHover: function () {
                $(window).scroll(function (e) {
                    // 滚动时停止hoverTimer
                    clearTimeout(Plugin.hoverTimer);
                    Plugin.isScrolling = true;
                });

                $(datatable.tableBody).find('.' + pfx + 'datatable__cell').off('mouseenter', 'mouseleave').on('mouseenter', function () {
                    // reset scroll timer to hover class
                    Plugin.hoverTimer = setTimeout(function () {
                        Plugin.isScrolling = false;
                    }, 200);
                    if (Plugin.isScrolling) return;

                    // normal table
                    let row = $(this).closest('.' + pfx + 'datatable__row').addClass(pfx + 'datatable__row--hover');
                    let index = $(row).index() + 1;

                    // 锁定表格
                    $(row).closest('.' + pfx + 'datatable__lock').parent().find('.' + pfx + 'datatable__row:nth-child(' + index + ')').addClass(pfx + 'datatable__row--hover');
                }).on('mouseleave', function () {
                    // 普通表格
                    let row = $(this).closest('.' + pfx + 'datatable__row').removeClass(pfx + 'datatable__row--hover');
                    let index = $(row).index() + 1;

                    // 锁定表格
                    $(row).closest('.' + pfx + 'datatable__lock').parent().find('.' + pfx + 'datatable__row:nth-child(' + index + ')').removeClass(pfx + 'datatable__row--hover');
                });
            },

            /**
             * Adjust width of locked table containers by resize handler
             * 大小改变时,调整锁定表容器宽度
             * @returns {number}
             */
            adjustLockContainer: function () {
                if (!Plugin.isLocked()) return 0;

                // refer to head dimension
                let containerWidth = $(datatable.tableHead).width();
                let lockLeft = $(datatable.tableHead).find('.' + pfx + 'datatable__lock--left').width();
                let lockRight = $(datatable.tableHead).find('.' + pfx + 'datatable__lock--right').width();

                if (typeof lockLeft === 'undefined') lockLeft = 0;
                if (typeof lockRight === 'undefined') lockRight = 0;

                let lockScroll = Math.floor(containerWidth - lockLeft - lockRight);
                $(datatable.table).find('.' + pfx + 'datatable__lock--scroll').css('width', lockScroll);

                return lockScroll;
            },

            /**
             * 拖拽调整大小
             *
             * todo; 暂未使用
             */
            dragResize: function () {
                let pressed = false;
                let start = undefined;
                let startX, startWidth;
                $(datatable.tableHead).find('.' + pfx + 'datatable__cell').mousedown(function (e) {
                    start = $(this);
                    pressed = true;
                    startX = e.pageX;
                    startWidth = $(this).width();
                    $(start).addClass(pfx + 'datatable__cell--resizing');

                }).mousemove(function (e) {
                    if (pressed) {
                        let i = $(start).index();
                        let tableBody = $(datatable.tableBody);
                        let ifLocked = $(start).closest('.' + pfx + 'datatable__lock');

                        if (ifLocked) {
                            let lockedIndex = $(ifLocked).index();
                            tableBody = $(datatable.tableBody).find('.' + pfx + 'datatable__lock').eq(lockedIndex);
                        }

                        $(tableBody).find('.' + pfx + 'datatable__row').each(function (tri, tr) {
                            $(tr).find('.' + pfx + 'datatable__cell').eq(i).width(startWidth + (e.pageX - startX)).children().width(startWidth + (e.pageX - startX));
                        });

                        $(start).children().css('width', startWidth + (e.pageX - startX));
                    }

                }).mouseup(function () {
                    $(start).removeClass(pfx + 'datatable__cell--resizing');
                    pressed = false;
                });

                $(document).mouseup(function () {
                    $(start).removeClass(pfx + 'datatable__cell--resizing');
                    pressed = false;
                });
            },

            /**
             * 在内容加载之前设置高度
             */
            initHeight: function () {
                 /**
                 * 获取表格高度
                 * @return {number} 高度
                 */
                let getHeight = function(){
                    if(util.isFunction(options.layout.height)){
                        return options.layout.height();
                    }else{
                        return options.layout.height;
                    }
                };

                if (options.layout.scroll) {
                    let theadHeight = $(datatable.tableHead).find('.' + pfx + 'datatable__row').outerHeight();
                    let tfootHeight = $(datatable.tableFoot).find('.' + pfx + 'datatable__row').outerHeight();
                    let bodyHeight = getHeight();
                    if (theadHeight > 0) {
                        bodyHeight -= theadHeight;
                    }
                    if (tfootHeight > 0) {
                        bodyHeight -= tfootHeight;
                    }

                    // 滚动条抵消
                    bodyHeight -= 2;

                    $(datatable.tableBody).css('max-height', bodyHeight);

                    // 设置可滚动区域的固定高度
                    $(datatable.tableBody).find('.' + pfx + 'datatable__lock--scroll').css('height', bodyHeight);
                }
            },

            /**
             * 设置基本DOM(table, thead, tbody, tfoot),如果不存在则创建
             */
            setupBaseDOM: function () {
                // 在初始化之前保持原始状态
                datatable.initialDatatable = $(datatable).clone();

                // 检查指定元素是否是table,如果不是就创建
                if ($(datatable).prop('tagName') === 'TABLE') {
                    // 如果初始化的元素是table,用div包裹
                    datatable.table = $(datatable).removeClass(pfx + 'datatable').addClass(pfx + 'datatable__table');
                    if ($(datatable.table).parents('.' + pfx + 'datatable').length === 0) {
                        datatable.table.wrap($('<div/>').addClass(pfx + 'datatable').addClass(pfx + 'datatable--' + options.layout.theme));
                        datatable.wrap = $(datatable.table).parent();
                    }
                } else {
                    // 创建表格
                    datatable.wrap = $(datatable).addClass(pfx + 'datatable').addClass(pfx + 'datatable--' + options.layout.theme);
                    datatable.table = $('<table/>').addClass(pfx + 'datatable__table').appendTo(datatable);
                }

                if (typeof options.layout.class !== 'undefined') {
                    $(datatable.wrap).addClass(options.layout.class);
                }

                $(datatable.table).removeClass(pfx + 'datatable--destroyed').css('display', 'block');

                // 如果表格id为空
                if (typeof $(datatable).attr('id') === 'undefined') {
                    // 禁用保存表格状态
                    Plugin.setOption('data.saveState', false);
                    // 设置唯一id
                    $(datatable.table).attr('id', util.getUniqueID(pfx + 'datatable--'));
                }

                // 设置表格最小高度
                if (Plugin.getOption('layout.minHeight'))
                    $(datatable.table).css('min-height', Plugin.getOption('layout.minHeight'));
                // 设置表格高度
                if (Plugin.getOption('layout.height'))
                    $(datatable.table).css('max-height', Plugin.getOption('layout.height'));

                if (options.data.type === null) {
                    // 移除表格width/display样式
                    $(datatable.table).css('width', '').css('display', '');
                }

                // 创建table > thead 元素
                datatable.tableHead = $(datatable.table).find('thead');
                if ($(datatable.tableHead).length === 0) {
                    datatable.tableHead = $('<thead/>').prependTo(datatable.table);
                }

                // 创建table > tbody 元素
                datatable.tableBody = $(datatable.table).find('tbody');
                if ($(datatable.tableBody).length === 0) {
                    datatable.tableBody = $('<tbody/>').appendTo(datatable.table);
                }
                // 创建table > tfoot 元素
                if (typeof options.layout.footer !== 'undefined' && options.layout.footer) {
                    datatable.tableFoot = $(datatable.table).find('tfoot');
                    if ($(datatable.tableFoot).length === 0) {
                        datatable.tableFoot = $('<tfoot/>').appendTo(datatable.table);
                    }
                }
            },

            /**
             * 设置列data属性
             */
            setupCellField: function (tableParts) {
                if (typeof tableParts === 'undefined') tableParts = $(datatable.table).children();
                let columns = options.columns;
                $.each(tableParts, function (part, tablePart) {
                    $(tablePart).find('.' + pfx + 'datatable__row').each(function (tri, tr) {
                        // prepare data
                        $(tr).find('.' + pfx + 'datatable__cell').each(function (tdi, td) {
                            if (typeof columns[tdi] !== 'undefined') {
                                $(td).data(columns[tdi]);
                            }
                        });
                    });
                });
            },

            /**
             * 执行列template回调
             *
             * @param tablePart
             */
            setupTemplateCell: function (tablePart) {
                if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
                let columns = options.columns;
                $(tablePart).find('.' + pfx + 'datatable__row').each(function (tri, tr) {
                    // 获取row上面的data-obj属性
                    let obj = $(tr).data('obj') || {};

                    // 执行template之前的回调函数
                    let beforeTemplate = Plugin.getOption('rows.beforeTemplate');
                    if (typeof beforeTemplate === 'function') {
                        beforeTemplate($(tr), obj, tri);
                    }
                    // 如果 data-obj 是 undefined, 从表中收集
                    if (typeof obj === 'undefined') {
                        obj = {};
                        $(tr).find('.' + pfx + 'datatable__cell').each(function (tdi, td) {
                            // 根据列名称获取列设置
                            let column = $.grep(columns, function (n, i) {
                                return $(td).data('field') === n.field;
                            })[0];
                            if (typeof column !== 'undefined') {
                                obj[column['field']] = $(td).text();
                            }
                        });
                    }

                    $(tr).find('.' + pfx + 'datatable__cell').each(function (tdi, td) {
                        // 根据列名称获取列设置
                        let column = $.grep(columns, function (n, i) {
                            return $(td).data('field') === n.field;
                        })[0];
                        if (typeof column !== 'undefined') {
                            if (typeof column.dictType !== 'undefined' && column.dictType != null) {
                                column.template = function (row) {
                                    let dicts = null;
                                    if (typeof column.dictType === 'string') {
                                        dicts = tool.getSysDictsObjectByDictType(column.dictType);
                                    } else if (util.isFunction(column.dictType)) {
                                        dicts = tool.getSysDictsObjectByDictType(column.dictType(row, tdi, datatable));
                                    } else {
                                        dicts = column.dictType;
                                    }
                                    return tool.getDictElement(ETool.getObject(column.field, row), dicts);
                                }
                            }
                            // 列 template
                            if (typeof column.template !== 'undefined') {
                                let finalValue = '';
                                // template 设置
                                if (typeof column.template === 'string') {
                                    finalValue = Plugin.dataPlaceholder(column.template, obj);
                                }
                                // template 回调函数
                                if (typeof column.template === 'function') {
                                    finalValue = column.template(obj, tri, datatable);
                                } else {
                                    // 如果引入了DOMPurify,用DOMPurify过滤xss
                                    if (typeof DOMPurify !== 'undefined') {
                                        finalValue = DOMPurify.sanitize(finalValue);
                                    }
                                }

                                let span = document.createElement('span');
                                span.innerHTML = finalValue;

                                // 用span包起来放到td中
                                $(td).html(span);

                                // 设置 span overflow
                                if (typeof column.overflow !== 'undefined') {
                                    $(span).css('overflow', column.overflow);
                                    $(span).css('position', 'relative');
                                }
                            } else {
                                // 给span设置title
                                $(td).find('span').attr({
                                    title: $(this).text()
                                })
                            }
                        }
                    });

                    // 行template之后的回调函数
                    let afterTemplate = Plugin.getOption('rows.afterTemplate');
                    if (typeof afterTemplate === 'function') {
                        afterTemplate($(tr), obj, tri);
                    }
                });

                $(tablePart).find('.table-actions').each(function () {
                    app.initTooltip($(this));
                });
            },

            /**
             * 设置额外的列属性
             * 比如: checkbox
             */
            setupSystemColumn: function () {
                datatable.dataSet = datatable.dataSet || [];
                // 无数据
                if (datatable.dataSet.length === 0) return;

                let columns = options.columns;
                $(datatable.tableBody).find('.' + pfx + 'datatable__row').each(function (tri, tr) {
                    $(tr).find('.' + pfx + 'datatable__cell').each(function (tdi, td) {
                        // 根据列名获取列设置
                        let column = $.grep(columns, function (n, i) {
                            return $(td).data('field') === n.field;
                        })[0];
                        if (typeof column !== 'undefined') {
                            let value = $(td).text();

                            // 启用列选择器
                            if (typeof column.selector !== 'undefined' && column.selector !== false) {
                                // 检查checkbox是否已经存在
                                if ($(td).find('.' + pfx + 'checkbox [type="checkbox"]').length > 0) return;

                                $(td).addClass(pfx + 'datatable__cell--check');

                                // 添加 checkbox
                                let chk = $('<label/>').addClass(pfx + 'checkbox ' + pfx + 'checkbox--single').append($('<input/>').attr('type', 'checkbox').attr('value', value).on('click', function () {
                                    if ($(this).is(':checked')) {
                                        // 添加已勾选class
                                        Plugin.setActive(this);
                                    } else {
                                        // 移除已勾选class
                                        Plugin.setInactive(this);
                                    }
                                })).append('&nbsp;<span></span>');

                                // 自定义class
                                if (typeof column.selector.class !== 'undefined') {
                                    $(chk).addClass(column.selector.class);
                                }

                                $(td).children().html(chk);
                            }

                            // 启用子表切换
                            if (typeof column.subtable !== 'undefined' && column.subtable) {
                                // 检查子表是否存
                                if ($(td).find('.' + pfx + 'datatable__toggle-subtable').length > 0) return;
                                // 添加切换
                                $(td).children().html($('<a/>').addClass(pfx + 'datatable__toggle-subtable').attr('href', '#').attr('data-value', value).append($('<i/>').addClass(Plugin.getOption('layout.icons.rowDetail.collapse'))));
                            }
                        }
                    });
                });

                /**
                 * 为header/footer初始化checkbox
                 * @param tr
                 */
                let initCheckbox = function (tr) {
                    // 获取列设置
                    let column = $.grep(columns, function (n, i) {
                        return typeof n.selector !== 'undefined' && n.selector !== false;
                    })[0];

                    if (typeof column !== 'undefined') {
                        // 启用列checkbox
                        if (typeof column.selector !== 'undefined' && column.selector !== false) {
                            let td = $(tr).find('[data-field="' + column.field + '"]');
                            // 检查checkbox是否已经存在
                            if ($(td).find('.' + pfx + 'checkbox [type="checkbox"]').length > 0) return;

                            $(td).addClass(pfx + 'datatable__cell--check');

                            // 添加 checkbox
                            let chk = $('<label/>').addClass(pfx + 'checkbox ' + pfx + 'checkbox--single ' + pfx + 'checkbox--all').append($('<input/>').attr('type', 'checkbox').on('click', function () {
                                if ($(this).is(':checked')) {
                                    Plugin.setActiveAll(true);
                                } else {
                                    Plugin.setActiveAll(false);
                                }
                            })).append('&nbsp;<span></span>');

                            // 自定义class
                            if (typeof column.selector.class !== 'undefined') {
                                $(chk).addClass(column.selector.class);
                            }

                            $(td).children().html(chk);
                        }
                    }
                };

                if (options.layout.header) {
                    initCheckbox($(datatable.tableHead).find('.' + pfx + 'datatable__row').first());
                }
                if (options.layout.footer) {
                    initCheckbox($(datatable.tableFoot).find('.' + pfx + 'datatable__row').first());
                }
            },

            /**
             * 调整宽度以匹配容器大小
             */
            adjustCellsWidth: function () {
                // 获取表格宽度
                let containerWidth = $(datatable.tableBody).innerWidth() - Plugin.iconOffset;

                // 获取总列数
                let columns = $(datatable.tableBody).find('.' + pfx + 'datatable__row:first-child').find('.' + pfx + 'datatable__cell').// exclude expand icon
                not('.' + pfx + 'datatable__toggle-detail').not(':hidden').length;

                if (columns > 0) {
                    //  删除保留排序图标宽度
                    containerWidth = containerWidth - (Plugin.iconOffset * columns);
                    let minWidth = Math.floor(containerWidth / columns);

                    // 最小宽度
                    if (minWidth <= Plugin.cellOffset) {
                        minWidth = Plugin.cellOffset;
                    }

                    let maxWidthList = {};
                    $(datatable.table).find('.' + pfx + 'datatable__row').find('.' + pfx + 'datatable__cell').// exclude expand icon
                    not('.' + pfx + 'datatable__toggle-detail').not(':hidden').each(function (tdi, td) {

                        let width = minWidth;
                        let dataWidth = $(td).data('width');

                        if (typeof dataWidth !== 'undefined') {

                            if (dataWidth === 'auto') {
                                let field = $(td).data('field');
                                if (maxWidthList[field]) {
                                    width = maxWidthList[field];
                                } else {
                                    let cells = $(datatable.table).find('.' + pfx + 'datatable__cell[data-field="' + field + '"]');
                                    width = maxWidthList[field] = Math.max.apply(null,
                                        $(cells).map(function () {
                                            return $(this).outerWidth();
                                        }).get());
                                }
                            } else {
                                width = dataWidth;
                            }
                        }
                        $(td).children().css('width', Math.ceil(width));
                    });
                }

                return datatable;
            },

            /**
             * 调整高度以匹配容器大小
             */
            adjustCellsHeight: function () {
                $.each($(datatable.table).children(), function (part, tablePart) {
                    let totalRows = $(tablePart).find('.' + pfx + 'datatable__row').first().parent().find('.' + pfx + 'datatable__row').length;
                    for (let i = 1; i <= totalRows; i++) {
                        let rows = $(tablePart).find('.' + pfx + 'datatable__row:nth-child(' + i + ')');
                        if ($(rows).length > 0) {
                            let maxHeight = Math.max.apply(null, $(rows).map(function () {
                                return $(this).outerHeight();
                            }).get());
                            $(rows).css('height', Math.ceil(maxHeight));
                        }
                    }
                });
            },

            /**
             * 设置table DOM class
             */
            setupDOM: function (table) {
                $(table).find('> thead').addClass(pfx + 'datatable__head');
                $(table).find('> tbody').addClass(pfx + 'datatable__body');
                $(table).find('> tfoot').addClass(pfx + 'datatable__foot');
                $(table).find('tr').addClass(pfx + 'datatable__row');
                $(table).find('tr > th, tr > td').addClass(pfx + 'datatable__cell');
                $(table).find('tr > th, tr > td').each(function (i, td) {
                    if ($(td).find('span').length === 0) {
                        $(td).wrapInner($('<span/>').css('width', Plugin.cellOffset));
                    }
                });
            },

            /**
             * 默认滚动条
             * @returns {{tableLocked: null, init: init, onScrolling: onScrolling}}
             */
            scrollbar: function () {
                let scroll = {
                    scrollable: null,
                    tableLocked: null,
                    initPosition: null,
                    init: function () {
                        let screen = util.getViewPort().width;
                        // 设置滚动条
                        if (options.layout.scroll) {
                            // 设置滚动class
                            $(datatable.wrap).addClass(pfx + 'datatable--scroll');

                            let scrollable = $(datatable.tableBody).find('.' + pfx + 'datatable__lock--scroll');

                            // 检查表格是否有数据
                            if ($(scrollable).find('.' + pfx + 'datatable__row').length > 0 && $(scrollable).length > 0) {
                                scroll.scrollHead = $(datatable.tableHead).find('> .' + pfx + 'datatable__lock--scroll > .' + pfx + 'datatable__row');
                                scroll.scrollFoot = $(datatable.tableFoot).find('> .' + pfx + 'datatable__lock--scroll > .' + pfx + 'datatable__row');
                                scroll.tableLocked = $(datatable.tableBody).find('.' + pfx + 'datatable__lock:not(.' + pfx + 'datatable__lock--scroll)');
                                if (Plugin.getOption('layout.customScrollbar') && util.detectIE() != 10 && screen > util.getBreakpoint('lg')) {
                                    scroll.initCustomScrollbar(scrollable[0]);
                                } else {
                                    scroll.initDefaultScrollbar(scrollable);
                                }
                            } else if ($(datatable.tableBody).find('.' + pfx + 'datatable__row').length > 0 && !datatable.isLocked()) {
                                scroll.scrollHead = $(datatable.tableHead).find('> .' + pfx + 'datatable__row');
                                scroll.scrollFoot = $(datatable.tableFoot).find('> .' + pfx + 'datatable__row');
                                if (Plugin.getOption('layout.customScrollbar') && util.detectIE() != 10 && screen > util.getBreakpoint('lg')) {
                                    scroll.initCustomScrollbar(datatable.tableBody);
                                } else {
                                    scroll.initDefaultScrollbar(datatable.tableBody);
                                }
                            }
                        }
                    },
                    initDefaultScrollbar: function (scrollable) {
                        // 获取初始坐标
                        scroll.initPosition = $(scrollable).scrollLeft();
                        $(scrollable).css('overflow-y', 'auto').off().on('scroll', scroll.onScrolling);
                        if (Plugin.getOption('rows.autoHide') !== true) {
                            $(scrollable).css('overflow-x', 'auto');
                        }
                    },
                    /**
                     * 滚动回调
                     * @param e
                     */
                    onScrolling: function (e) {
                        let left = $(this).scrollLeft();
                        let top = $(this).scrollTop();
                        $(scroll.scrollHead).css('left', -left);
                        $(scroll.scrollFoot).css('left', -left);
                        $(scroll.tableLocked).each(function (i, table) {
                            if (Plugin.isLocked()) {
                                // scrollbar offset
                                top -= 1;
                            }
                            $(table).css('top', -top);
                        });
                    },
                    initCustomScrollbar: function (scrollable) {
                        scroll.scrollable = scrollable;
                        // create a new instance for table body with scrollbar
                        Plugin.initScrollbar(scrollable);
                        // 获取初始坐标
                        scroll.initPosition = $(scrollable).scrollLeft();
                        $(scrollable).off().on('scroll', scroll.onScrolling);
                    }
                };
                scroll.init();
                return scroll;
            },

            /**
             * 初始化滚动条和复位位置
             * @param element
             * @param options
             */
            initScrollbar: function (element, options) {
                if (!element || !element.nodeName) {
                    return;
                }
                $(datatable.tableBody).css('overflow', '');
                if ($(element).hasClass('ps')) {
                    $(element).data('ps').update();
                } else {
                    let ps = new PerfectScrollbar(element, Object.assign({}, {
                        wheelSpeed: 0.5,
                        swipeEasing: true,
                        // wheelPropagation: false,
                        minScrollbarLength: 40,
                        maxScrollbarLength: 300,
                        suppressScrollX: Plugin.getOption('rows.autoHide') && !Plugin.isLocked()
                    }, options));
                    $(element).data('ps', ps).addClass('ps--focus');
                    datatable.scrollbarElement = element;

                    // 拖动窗口大小重新设置滚动条
                    $(window).resize(function () {
                        ps.update();
                    });
                }
            },

            /**
             * 根据options.columns设置表头标题
             */
            setHeadTitle: function (tablePart) {
                if (typeof tablePart === 'undefined') tablePart = datatable.tableHead;
                tablePart = $(tablePart)[0];
                let columns = options.columns;
                let row = tablePart.getElementsByTagName('tr')[0];
                let ths = tablePart.getElementsByTagName('td');

                if (typeof row === 'undefined') {
                    row = document.createElement('tr');
                    tablePart.appendChild(row);
                }

                $.each(columns, function (i, column) {
                    let th = ths[i];
                    if (typeof th === 'undefined') {
                        th = document.createElement('th');
                        row.appendChild(th);
                    }

                    // 设置列标题
                    if (typeof column['title'] !== 'undefined') {
                        th.innerHTML = column.title;
                        th.setAttribute('data-field', column.field);
                        $(th).addClass(column.class);
                        // set disable autoHide or force enable
                        if (typeof column.autoHide !== 'undefined') {
                            if (column.autoHide !== true) {
                                th.setAttribute('data-autohide-disabled', column.autoHide);
                            } else {
                                th.setAttribute('data-autohide-enabled', column.autoHide);
                            }
                        }
                        $(th).data(column);
                    }

                    // 设置 header attr 属性
                    if (typeof column.attr !== 'undefined') {
                        $.each(column.attr, function (key, val) {
                            th.setAttribute(key, val);
                        });
                    }

                    // 为thead/tfoot添加文本对齐方式
                    if (typeof column.textAlign !== 'undefined') {
                        let align = typeof datatable.textAlign[column.textAlign] !== 'undefined' ? datatable.textAlign[column.textAlign] : '';
                        $(th).addClass(align);
                    }
                });
                Plugin.setupDOM(tablePart);
            },

            /**
             * 通过Ajax获取数据或本地数据
             */
            dataRender: function (action) {
                $(datatable.table).siblings('.' + pfx + 'datatable__pager').removeClass(pfx + 'datatable--paging-loaded');
                /**
                 * 构建参数
                 * @return {*}
                 */
                let buildMeta = function () {
                    datatable.dataSet = datatable.dataSet || [];
                    Plugin.localDataUpdate();
                    // local pagination meta
                    let meta = Plugin.getDataSourceParam('page');
                    if (meta.size == null || meta.size === 0) {
                        meta.size = options.data.pageSize || 10;
                    }
                    if (meta.current == null || meta.current === 0) {
                        meta.current = 1;
                    }
                    meta.total = datatable.dataSet.length;
                    let start = Math.max(meta.size * (meta.current - 1), 0);
                    let end = Math.min(start + meta.size, meta.total);
                    datatable.dataSet = $(datatable.dataSet).slice(start, end);
                    return meta;
                };
                /**
                 * 获取数据后
                 * @param result
                 */
                let afterGetData = function (result) {
                    let _result = null;
                    if(result != null){
                        _result = tool.analysisCode(result.code);
                    }
                    if (result == null || (_result && _result.code === resultCode.code.success)) {
                        let localPagingCallback = function (ctx, meta) {
                            if (!$(ctx.pager).hasClass(pfx + 'datatable--paging-loaded')) {
                                $(ctx.pager).remove();
                                ctx.init(meta);
                            }
                            $(ctx.pager).off().on(pfx + 'datatable--on-goto-page', function (e) {
                                $(ctx.pager).remove();
                                ctx.init(meta);
                            });

                            let start = Math.max(meta.size * (meta.current - 1), 0);
                            let end = Math.min(start + meta.size, meta.total);

                            Plugin.localDataUpdate();
                            datatable.dataSet = $(datatable.dataSet).slice(start, end);

                            // 将数据插入到表格中
                            Plugin.insertData();
                        };
                        $(datatable.wrap).removeClass(pfx + 'datatable--error');
                        // 启用分页
                        if (options.pagination) {
                            if (options.data.serverPaging && options.data.type !== 'local') {
                                // 服务器端分页
                                if (_result && _result.code !== resultCode.code.success) {
                                    tool.errorTip('查询数据失败', result.message);
                                    result.data = [];
                                    result.data.current = 0;
                                    result.data.size = 15;
                                    result.data.total = 0;
                                }
                                let serverMeta = result.data;
                                if (serverMeta !== null) {
                                    Plugin.pagingObject = Plugin.paging(serverMeta);
                                } else {
                                    // 没有来自服务器响应的分页，使用本地分页
                                    Plugin.pagingObject = Plugin.paging(buildMeta(), localPagingCallback);
                                }
                            } else {
                                // local pagination can be used by remote data also
                                // 本地分页也可由远程数据使用
                                Plugin.pagingObject = Plugin.paging(buildMeta(), localPagingCallback);
                            }
                        } else {
                            // 禁用分页
                            Plugin.localDataUpdate();
                        }
                        // 将数据插入到表格中
                        Plugin.insertData();
                    }
                };

                // 数据在本地
                if (options.data.type === 'local'
                    || options.data.serverSorting === false && action === 'sort'
                    || options.data.serverFiltering === false && action === 'search'
                ) {
                    setTimeout(function () {
                        afterGetData();
                        Plugin.setAutoColumns();
                    });
                    return;
                }

                // 获取远程数据
                Plugin.getData().done(afterGetData);
            },

            /**
             * 插入ajax数据
             */
            insertData: function () {
                datatable.dataSet = datatable.dataSet || [];
                let params = Plugin.getDataSourceParam();

                // 获取行属性
                let pagination = params.page;
                let start = (Math.max(pagination.current, 1) - 1) * pagination.size;
                let end = Math.min(pagination.current, pagination.pages) * pagination.size;
                let rowProps = {};
                if (typeof options.data.attr.rowProps !== 'undefined' && options.data.attr.rowProps.length) {
                    rowProps = options.data.attr.rowProps.slice(start, end);
                }

                let tableBody = document.createElement('tbody');
                tableBody.style.visibility = 'hidden';
                let colLength = options.columns.length;

                $.each(datatable.dataSet, function (rowIndex, row) {
                    let tr = document.createElement('tr');
                    tr.setAttribute('data-row', rowIndex);
                    if (typeof row.id !== 'undefined') {
                        tr.setAttribute('data-id', row.id);
                    }
                    // 设置tr上的data-obj
                    $(tr).data('obj', row);

                    if (typeof rowProps[rowIndex] !== 'undefined') {
                        $.each(rowProps[rowIndex], function () {
                            tr.setAttribute(this.name, this.value);
                        });
                    }

                    for (let a = 0; a < colLength; a += 1) {
                        let column = options.columns[a];
                        let classes = [];
                        // 添加排序class
                        if (Plugin.getObject('sort.field', params) === column.field) {
                            classes.push(pfx + 'datatable__cell--sorted');
                        }

                        // 设置文本对齐方式
                        if (typeof column.textAlign !== 'undefined') {
                            let align = typeof datatable.textAlign[column.textAlign] !== 'undefined' ? datatable.textAlign[column.textAlign] : '';
                            classes.push(align);
                        }

                        // let classAttr = '';
                        // 设置列class
                        if (typeof column.class !== 'undefined') {
                            classes.push(column.class);
                        }

                        let td = document.createElement('td');
                        $(td).addClass(classes);
                        td.setAttribute('data-field', column.field);
                        // set disable autoHide or force enable
                        // 设置禁用自动隐藏或强制启用
                        if (typeof column.autoHide !== 'undefined') {
                            if (column.autoHide !== true) {
                                td.setAttribute('data-autohide-disabled', column.autoHide);
                            } else {
                                td.setAttribute('data-autohide-enabled', column.autoHide);
                            }
                        }
                        td.innerHTML = Plugin.getObject(column.field, row);
                        tr.appendChild(td);
                    }

                    tableBody.appendChild(tr);
                });

                // 显示无记录消息
                if (datatable.dataSet.length === 0) {
                    let errorSpan = document.createElement('span');
                    $(errorSpan).addClass(pfx + 'datatable--error');
                    errorSpan.innerHTML = Plugin.getOption('translate.records.noRecords');
                    tableBody.appendChild(errorSpan);
                    $(datatable.wrap).addClass(pfx + 'datatable--error ' + pfx + 'datatable--loaded');
                    Plugin.spinnerCallback(false);
                }

                // 替换已存在的table body
                $(datatable.tableBody).replaceWith(tableBody);
                datatable.tableBody = tableBody;

                // 更新布局
                Plugin.setupDOM(datatable.table);
                Plugin.setupCellField([datatable.tableBody]);
                Plugin.setupTemplateCell(datatable.tableBody);
                Plugin.layoutUpdate();
            },
            /**
             * 更新table组件
             */
            updateTableComponents: function () {
                datatable.tableHead = $(datatable.table).children('thead');
                datatable.tableBody = $(datatable.table).children('tbody');
                datatable.tableFoot = $(datatable.table).children('tfoot');
            },

            /**
             * 使用ajax获取数据
             */
            getData: function () {
                // Plugin.spinnerCallback(true);

                let ajaxParams = {
                    contentType: 'application/json',
                    dataType: 'json',
                    method: 'POST',
                    data: {},
                    timeout: Plugin.getOption('data.source.read.timeout') || 1000 * 30
                };

                if (options.data.type === 'local') {
                    ajaxParams.url = options.data.source;
                }

                if (options.data.type === 'remote') {
                    let data = Plugin.getDataSourceParam();
                    // 如果没有启用服务端分页,删除参数中的分页信息
                    if (!Plugin.getOption('data.serverPaging')) {
                        delete data['page'];
                    } else {
                        // 如果数据来源于服务器并且在服务器分页,将排序信息放到参数中
                        if (typeof data.sort !== 'undefined') {
                            // 这里使用sortField，数据库字段可能和field不一样
                            if ('asc' === data.sort.sort) {
                                data.page.ascs = [data.sort.sortField];
                            } else if ('desc' === data.sort.sort) {
                                data.page.descs = [data.sort.sortField];
                            }
                        }
                        delete data['sort'];
                    }

                    // 表单内参数是否需要带入
                    if (Plugin.getOption('data.source.autoQuery')) {
                        let formParams = tool.queryParams($(datatable.table).parents('form.e-form').find(Plugin.getOption('search.input')));
                        ajaxParams.data = $.extend(true, ajaxParams.data, formParams);
                    }

                    ajaxParams.data = $.extend({}, ajaxParams.data, data, Plugin.getOption('data.source.read.params'));
                    ajaxParams = $.extend({}, ajaxParams, Plugin.getOption('data.source.read'));

                    if (typeof ajaxParams.url !== 'string') ajaxParams.url = Plugin.getOption('data.source.read');
                    if (typeof ajaxParams.url !== 'string') ajaxParams.url = Plugin.getOption('data.source');
                    ajaxParams.method = Plugin.getOption('data.source.read.method') || 'POST';
                    ajaxParams.data = JSON.stringify(ajaxParams.data);
                }
                /**
                 * 失败
                 * @param message
                 */
                let failBack = function (message) {
                    $(datatable).trigger(pfx + 'datatable--on-ajax-fail', message);
                    $(datatable.tableBody).html($('<span/>').addClass(pfx + 'datatable--error').html(Plugin.getOption('translate.records.noRecords')));
                    $(datatable.wrap).addClass(pfx + 'datatable--error ' + pfx + 'datatable--loaded');
                    Plugin.spinnerCallback(false);
                };

                return $.ajax(ajaxParams).done(function (response, textStatus, jqXHR) {
                    // 解析响应码
                    let result = ETool.analysisCode(response.code);
                    if (resultCode.code.success === result.code) {
                        datatable.lastResponse = response;
                        // 从响应数据中获取数据
                        datatable.dataSet = datatable.originalDataSet = Plugin.dataMapCallback(response);
                        Plugin.setAutoColumns();
                        $(datatable).trigger(pfx + 'datatable--on-ajax-done', [datatable.dataSet]);
                    } else {
                        util.handleResult(response, function () {
                            failBack(response.message);
                        });
                    }
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    util.handleResult(jqXHR.responseJSON, function () {
                        failBack(jqXHR.responseJSON.message);
                    });
                }).always(function () {
                });
            },

            /**
             * 分页
             * @param meta if null, 本地分页, 否则服务器分页
             * @param callback 回调
             */
            paging: function (meta, callback) {
                let pg = {
                    meta: null,
                    pager: null,
                    paginateEvent: null,
                    pagerLayout: {pagination: null, info: null},
                    callback: null,
                    init: function (meta) {
                        pg.meta = meta;

                        // 转为int类型
                        pg.meta.current = parseInt(pg.meta.current);
                        pg.meta.pages = parseInt(pg.meta.pages);
                        pg.meta.size = parseInt(pg.meta.size);
                        pg.meta.total = parseInt(pg.meta.total);

                        // 计算总页数
                        pg.meta.pages = Math.max(Math.ceil(pg.meta.total / pg.meta.size), 1);

                        // 当前页不能超过总页数
                        if (pg.meta.current > pg.meta.pages) pg.meta.current = pg.meta.pages;

                        // 设置唯一事件
                        pg.paginateEvent = Plugin.getTablePrefix();

                        pg.pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager');
                        if ($(pg.pager).hasClass(pfx + 'datatable--paging-loaded')) return;

                        // 重新创建分页
                        $(pg.pager).remove();

                        // 无分页
                        if (pg.meta.pages === 0) return;

                        // 设置分页参数
                        Plugin.setDataSourceParam('page', {
                            current: pg.meta.current,
                            pages: pg.meta.pages,
                            size: pg.meta.size,
                            total: pg.meta.total
                        });

                        // 默认回调函数, 包含服务器分页
                        pg.callback = pg.serverCallback;
                        // 自定义回调函数
                        if (typeof callback === 'function') pg.callback = callback;

                        pg.addPaginateEvent();
                        pg.populate();

                        pg.meta.current = Math.max(pg.meta.current || 1, pg.meta.current);

                        $(datatable).trigger(pg.paginateEvent, pg.meta);

                        pg.pagingBreakpoint.call();
                        $(window).resize(pg.pagingBreakpoint);
                    },
                    serverCallback: function (ctx, meta) {
                        Plugin.dataRender();
                    },
                    /**
                     * 生成分页工具条
                     */
                    populate: function () {
                        let icons = Plugin.getOption('layout.icons.pagination');
                        let title = Plugin.getOption('translate.toolbar.pagination.items.default');
                        // 分页根元素
                        pg.pager = $('<div/>').addClass(pfx + 'datatable__pager ' + pfx + 'datatable--paging-loaded');
                        // 页码链接
                        let pagerNumber = $('<ul/>').addClass(pfx + 'datatable__pager-nav');
                        pg.pagerLayout['pagination'] = pagerNumber;

                        // 第一页/上一页 按钮
                        $('<li/>').append($('<a/>').attr('title', title.first).addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--first').append($('<i/>').addClass(icons.first)).on('click', pg.gotoMorePage).attr('data-page', 1)).appendTo(pagerNumber);
                        $('<li/>').append($('<a/>').attr('title', title.prev).addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--prev').append($('<i/>').addClass(icons.prev)).on('click', pg.gotoMorePage)).appendTo(pagerNumber);

                        // more previous pages
                        $('<li/>').append($('<a/>').attr('title', title.more).addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--more-prev').html($('<i/>').addClass(icons.more)).on('click', pg.gotoMorePage)).appendTo(pagerNumber);

                        $('<li/>').append($('<input/>').attr('type', 'text').addClass(pfx + 'pager-input form-control').attr('title', title.input).on('keyup', function () {
                            // 当 keyup 更新 [data-page]
                            $(this).attr('data-page', Math.abs($(this).val()));
                        }).on('keypress', function (e) {
                            // 按回车
                            if (e.which === 13) pg.gotoMorePage(e);
                        })).appendTo(pagerNumber);

                        let pagesNumber = Plugin.getOption('toolbar.items.pagination.pages.desktop.pagesNumber');
                        let end = Math.ceil(pg.meta.current / pagesNumber) * pagesNumber;
                        let start = end - pagesNumber;
                        if (end > pg.meta.pages) {
                            end = pg.meta.pages;
                        }
                        for (let x = start; x < end; x++) {
                            let pageNumber = x + 1;
                            $('<li/>').append($('<a/>').addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link-number').text(pageNumber).attr('data-page', pageNumber).attr('title', pageNumber).on('click', pg.gotoPage)).appendTo(pagerNumber);
                        }

                        // more next pages
                        $('<li/>').append($('<a/>').attr('title', title.more).addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--more-next').html($('<i/>').addClass(icons.more)).on('click', pg.gotoMorePage)).appendTo(pagerNumber);

                        // 下一页/最后一页 按钮
                        $('<li/>').append($('<a/>').attr('title', title.next).addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--next').append($('<i/>').addClass(icons.next)).on('click', pg.gotoMorePage)).appendTo(pagerNumber);
                        $('<li/>').append($('<a/>').attr('title', title.last).addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--last').append($('<i/>').addClass(icons.last)).on('click', pg.gotoMorePage).attr('data-page', pg.meta.pages)).appendTo(pagerNumber);

                        // 分页信息
                        if (Plugin.getOption('toolbar.items.info')) {
                            pg.pagerLayout['info'] = $('<div/>').addClass(pfx + 'datatable__pager-info').append($('<span/>').addClass(pfx + 'datatable__pager-detail'));
                        }

                        $.each(Plugin.getOption('toolbar.layout'), function (i, layout) {
                            $(pg.pagerLayout[layout]).appendTo(pg.pager);
                        });

                        // 页大小 select
                        let pageSizeSelect = $('<select/>').addClass('selectpicker ' + pfx + 'datatable__pager-size').attr('title', Plugin.getOption('translate.toolbar.pagination.items.default.select')).attr('data-width', '65px').val(pg.meta.size).on('change', pg.updatePerpage).prependTo(pg.pagerLayout['info']);

                        let pageSizes = Plugin.getOption('toolbar.items.pagination.pageSizeSelect');
                        // 如果未指定页大小设置,使用默认设置
                        if (pageSizes.length === 0) pageSizes = [10, 15, 20, 30, 50, 100];
                        $.each(pageSizes, function (i, size) {
                            let display = size;
                            if (size === -1) display = Plugin.getOption('translate.toolbar.pagination.items.default.all');
                            $('<option/>').attr('value', size).html(display).appendTo(pageSizeSelect);
                        });

                        // 初始化下拉插件
                        $(datatable).ready(function () {
                            $('.selectpicker').selectpicker().on('hide.bs.select', function () {
                                // fix dropup arrow icon on hide
                                $(this).closest('.bootstrap-select').removeClass('dropup');
                            }).siblings('.dropdown-toggle').attr('title', Plugin.getOption('translate.toolbar.pagination.items.default.select'));
                        });

                        pg.paste();
                    },
                    /**
                     * 将分页工具条插入页码
                     */
                    paste: function () {
                        // 根据toolbar.placement(top|bottom)插入分页工具条
                        $.each($.unique(Plugin.getOption('toolbar.placement')),
                            function (i, position) {
                                if (position === 'bottom') {
                                    $(pg.pager).clone(true).insertAfter(datatable.table);
                                }
                                if (position === 'top') {
                                    // 分页放到顶部需要额外的空间
                                    $(pg.pager).clone(true).addClass(pfx + 'datatable__pager--top').insertBefore(datatable.table);
                                }
                            });
                    },
                    gotoMorePage: function (e) {
                        e.preventDefault();
                        // $(this) is a link of .'+pfx+'datatable__pager-link

                        if ($(this).attr('disabled') === 'disabled') return false;

                        let page = $(this).attr('data-page');

                        // event from text input
                        if (typeof page === 'undefined') {
                            page = $(e.target).attr('data-page');
                        }

                        pg.openPage(parseInt(page));
                        return false;
                    },
                    /**
                     * 点击页码按钮
                     *
                     * @param e
                     */
                    gotoPage: function (e) {
                        e.preventDefault();
                        // 如果点击的是当前页,忽略
                        if ($(this).hasClass(pfx + 'datatable__pager-link--active')) return;

                        pg.openPage(parseInt($(this).data('page')));
                    },
                    /**
                     * 跳转到多少页
                     * @param page {string} 页码
                     */
                    openPage: function (page) {
                        // 当前页从1开始
                        pg.meta.current = parseInt(page);

                        $(datatable).trigger(pg.paginateEvent, pg.meta);
                        // 默认回调函数, 包含服务器分页
                        pg.callback = pg.serverCallback;
                        pg.callback(pg, pg.meta);

                        // 更新分页回调函数
                        $(pg.pager).trigger(pfx + 'datatable--on-goto-page', pg.meta);
                    },
                    /**
                     * 改变页大小
                     * @param e
                     */
                    updatePerpage: function (e) {
                        e.preventDefault();

                        pg.pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager').removeClass(pfx + 'datatable--paging-loaded');

                        // 改变页大小
                        if (e.originalEvent) {
                            pg.meta.size = parseInt($(this).val());
                        }

                        $(pg.pager).find('select.' + pfx + 'datatable__pager-size').val(pg.meta.size).attr('data-selected', pg.meta.size);

                        // 更改页大小后返回第一页
                        pg.meta.current = 1;
                        // 更新 dataSource 参数
                        Plugin.setDataSourceParam('page', {
                            current: pg.meta.current,
                            pages: pg.meta.pages,
                            size: pg.meta.size,
                            total: pg.meta.total
                        });

                        // 更新分页回调函数
                        $(pg.pager).trigger(pfx + 'datatable--on-update-perpage', pg.meta);
                        $(datatable).trigger(pg.paginateEvent, pg.meta);
                        pg.callback(pg, pg.meta);

                        // 更新分页信息
                        pg.updateInfo.call();
                    },
                    /**
                     * 绑定分页事件
                     *
                     * @param e
                     */
                    addPaginateEvent: function (e) {
                        $(datatable).off(pg.paginateEvent).on(pg.paginateEvent, function (e, meta) {
                            Plugin.spinnerCallback(true);

                            pg.pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager');
                            let pagerNumber = $(pg.pager).find('.' + pfx + 'datatable__pager-nav');

                            // 设置当前页按钮状态
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--active').removeClass(pfx + 'datatable__pager-link--active');
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link-number[data-page="' + meta.current + '"]').addClass(pfx + 'datatable__pager-link--active');

                            // 设置上一页下一页按钮页码
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--prev').attr('data-page', Math.max(meta.current - 1, 1));
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--next').attr('data-page', Math.min(meta.current + 1, meta.pages));

                            // 设置当前页页码
                            $(pg.pager).each(function () {
                                $(this).find('.' + pfx + 'pager-input[type="text"]').prop('value', meta.current);
                            });

                            $(pg.pager).find('.' + pfx + 'datatable__pager-nav').show();
                            if (meta.pages <= 1) {
                                // 如果不足2页,隐藏工具条
                                $(pg.pager).find('.' + pfx + 'datatable__pager-nav').hide();
                            }

                            // 更新 dataSource 参数
                            Plugin.setDataSourceParam('page', {
                                current: pg.meta.current,
                                pages: pg.meta.pages,
                                size: pg.meta.size,
                                total: pg.meta.total
                            });

                            $(pg.pager).find('select.' + pfx + 'datatable__pager-size').val(meta.size).attr('data-selected', meta.size);

                            // 清除选中行
                            $(datatable.table).find('.' + pfx + 'checkbox > [type="checkbox"]').prop('checked', false);
                            $(datatable.table).find('.' + pfx + 'datatable__row--active').removeClass(pfx + 'datatable__row--active');

                            pg.updateInfo.call();
                            pg.pagingBreakpoint.call();
                            // Plugin.resetScroll();
                        });
                    },
                    /**
                     * 更新分页信息
                     */
                    updateInfo: function () {
                        let start = Math.max(pg.meta.size * (pg.meta.current - 1) + 1, 1);
                        let end = Math.min(start + pg.meta.size - 1, pg.meta.total);
                        // 更新分页信息
                        $(pg.pager).find('.' + pfx + 'datatable__pager-info').find('.' + pfx + 'datatable__pager-detail').html(Plugin.dataPlaceholder(
                            Plugin.getOption('translate.toolbar.pagination.items.info'), {
                                start: start,
                                end: pg.meta.size === -1 ? pg.meta.total : end,
                                pageSize: pg.meta.size === -1 ||
                                pg.meta.size >= pg.meta.total
                                    ? pg.meta.total
                                    : pg.meta.size,
                                total: pg.meta.total,
                            }));
                    },

                    /**
                     * 根据当前屏幕尺寸更新分页工具条显示方式
                     */
                    pagingBreakpoint: function () {
                        // keep page links reference
                        let pagerNumber = $(datatable.table).siblings('.' + pfx + 'datatable__pager').find('.' + pfx + 'datatable__pager-nav');
                        if ($(pagerNumber).length === 0) return;

                        let currentPage = Plugin.getCurrentPage();
                        let pagerInput = $(pagerNumber).find('.' + pfx + 'pager-input').closest('li');

                        // 重置
                        $(pagerNumber).find('li').show();

                        // 更新分页工具条
                        $.each(Plugin.getOption('toolbar.items.pagination.pages'),
                            function (mode, option) {
                                if (util.isInResponsiveRange(mode)) {
                                    switch (mode) {
                                        case 'desktop':
                                        case 'tablet':
                                            let end = Math.ceil(currentPage / option.pagesNumber) *
                                                option.pagesNumber;
                                            // let start = end - option.pagesNumber;
                                            $(pagerInput).hide();
                                            pg.meta = Plugin.getDataSourceParam('page');
                                            pg.paginationUpdate();
                                            break;

                                        case 'mobile':
                                            $(pagerInput).show();
                                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--more-prev').closest('li').hide();
                                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--more-next').closest('li').hide();
                                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link-number').closest('li').hide();
                                            break;
                                    }

                                    return false;
                                }
                            });
                    },

                    /**
                     * Update pagination number and button display
                     */
                    paginationUpdate: function () {
                        let pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager').find('.' + pfx + 'datatable__pager-nav'),
                            pagerMorePrev = $(pager).find('.' + pfx + 'datatable__pager-link--more-prev'),
                            pagerMoreNext = $(pager).find('.' + pfx + 'datatable__pager-link--more-next'),
                            pagerFirst = $(pager).find('.' + pfx + 'datatable__pager-link--first'),
                            pagerPrev = $(pager).find('.' + pfx + 'datatable__pager-link--prev'),
                            pagerNext = $(pager).find('.' + pfx + 'datatable__pager-link--next'),
                            pagerLast = $(pager).find('.' + pfx + 'datatable__pager-link--last');

                        // 获取可见页码
                        let pagerNumber = $(pager).find('.' + pfx + 'datatable__pager-link-number');
                        // 获取第一个页码的上一页页码
                        let morePrevPage = Math.max($(pagerNumber).first().data('page') - 1,
                            1);
                        $(pagerMorePrev).each(function (i, prev) {
                            $(prev).attr('data-page', morePrevPage);
                        });
                        // 判断是否要显示上一页按钮
                        if (morePrevPage === 1) {
                            $(pagerMorePrev).parent().hide();
                        } else {
                            $(pagerMorePrev).parent().show();
                        }

                        // 获取最后一个页码的下一页页码
                        let moreNextPage = Math.min($(pagerNumber).last().data('page') + 1,
                            pg.meta.pages);
                        $(pagerMoreNext).each(function (i, prev) {
                            $(pagerMoreNext).attr('data-page', moreNextPage).show();
                        });

                        // 判断是否要显示下一页按钮
                        if (moreNextPage === pg.meta.pages
                            // missing dot fix when last hidden page is one left
                            && moreNextPage === $(pagerNumber).last().data('page')) {
                            $(pagerMoreNext).parent().hide();
                        } else {
                            $(pagerMoreNext).parent().show();
                        }

                        // 第一页/最后一页按钮状态
                        if (pg.meta.current === 1) {
                            $(pagerFirst).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                            $(pagerPrev).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                        } else {
                            $(pagerFirst).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                            $(pagerPrev).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                        }
                        if (pg.meta.current === pg.meta.pages) {
                            $(pagerNext).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                            $(pagerLast).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                        } else {
                            $(pagerNext).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                            $(pagerLast).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                        }

                        // 根据配置设置按钮显示/隐藏
                        let nav = Plugin.getOption('toolbar.items.pagination.navigation');
                        if (!nav.first) $(pagerFirst).remove();
                        if (!nav.prev) $(pagerPrev).remove();
                        if (!nav.next) $(pagerNext).remove();
                        if (!nav.last) $(pagerLast).remove();
                    }
                };
                pg.init(meta);
                return pg;
            },

            /**
             * 根据屏幕尺寸与设置,隐藏/显示列
             * options[columns][i][responsive][visible/hidden]
             */
            columnHide: function () {
                let screen = util.getViewPort().width;
                // foreach columns setting
                $.each(options.columns, function (i, column) {
                    if (typeof column.responsive !== 'undefined') {
                        let field = column.field;
                        let tds = $.grep($(datatable.table).find('.' + pfx + 'datatable__cell'), function (n, i) {
                            return field === $(n).data('field');
                        });
                        if (util.getBreakpoint(column.responsive.hidden) >= screen) {
                            $(tds).hide();
                        } else {
                            $(tds).show();
                        }
                        if (util.getBreakpoint(column.responsive.visible) <= screen) {
                            $(tds).show();
                        } else {
                            $(tds).hide();
                        }
                    }
                });
            },

            /**
             * 设置子表
             */
            setupSubDatatable: function () {
                let subTableCallback = Plugin.getOption('detail.content');
                if (typeof subTableCallback !== 'function') return;

                // subtable already exist
                if ($(datatable.table).find('.' + pfx + 'datatable__subtable').length > 0) return;

                $(datatable.wrap).addClass(pfx + 'datatable--subtable');

                options.columns[0]['subtable'] = true;

                // toggle on open sub table
                let toggleSubTable = function (e) {
                    e.preventDefault();
                    // get parent row of this subtable
                    let parentRow = $(this).closest('.' + pfx + 'datatable__row');

                    // get subtable row for sub table
                    let subTableRow = $(parentRow).next('.' + pfx + 'datatable__row-subtable');
                    if ($(subTableRow).length === 0) {
                        // prepare DOM for sub table, each <tr> as parent and add <tr> as child table
                        subTableRow = $('<tr/>').addClass(pfx + 'datatable__row-subtable ' + pfx + 'datatable__row-loading').hide().append($('<td/>').addClass(pfx + 'datatable__subtable').attr('colspan', Plugin.getTotalColumns()));
                        $(parentRow).after(subTableRow);
                        // add class to even row
                        if ($(parentRow).hasClass(pfx + 'datatable__row--even')) {
                            $(subTableRow).addClass(pfx + 'datatable__row-subtable--even');
                        }
                    }

                    $(subTableRow).toggle();

                    let subTable = $(subTableRow).find('.' + pfx + 'datatable__subtable');

                    // get id from first column of parent row
                    let primaryKey = $(this).closest('[data-field]:first-child').find('.' + pfx + 'datatable__toggle-subtable').data('value');

                    let icon = $(this).find('i').removeAttr('class');

                    // prevent duplicate datatable init
                    if ($(parentRow).hasClass(pfx + 'datatable__row--subtable-expanded')) {
                        $(icon).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'));
                        // remove expand class from parent row
                        $(parentRow).removeClass(pfx + 'datatable__row--subtable-expanded');
                        // trigger event on collapse
                        $(datatable).trigger(pfx + 'datatable--on-collapse-subtable', [parentRow]);
                    } else {
                        // expand and run callback function
                        $(icon).addClass(Plugin.getOption('layout.icons.rowDetail.expand'));
                        // add expand class to parent row
                        $(parentRow).addClass(pfx + 'datatable__row--subtable-expanded');
                        // trigger event on expand
                        $(datatable).trigger(pfx + 'datatable--on-expand-subtable', [parentRow]);
                    }

                    // prevent duplicate datatable init
                    if ($(subTable).find('.' + pfx + 'datatable').length === 0) {
                        // get data by primary id
                        $.map(datatable.dataSet, function (n, i) {
                            // primary id must be at the first column, otherwise e.data will be undefined
                            if (primaryKey === n[options.columns[0].field]) {
                                e.data = n;
                                return true;
                            }
                            return false;
                        });

                        // deprecated in v5.0.6
                        e.detailCell = subTable;

                        e.parentRow = parentRow;
                        e.subTable = subTable;

                        // run callback with event
                        subTableCallback(e);

                        $(subTable).children('.' + pfx + 'datatable').on(pfx + 'datatable--on-init', function (e) {
                            $(subTableRow).removeClass(pfx + 'datatable__row-loading');
                        });
                        if (Plugin.getOption('data.type') === 'local') {
                            $(subTableRow).removeClass(pfx + 'datatable__row-loading');
                        }
                    }
                };

                let columns = options.columns;
                $(datatable.tableBody).find('.' + pfx + 'datatable__row').each(function (tri, tr) {
                    $(tr).find('.' + pfx + 'datatable__cell').each(function (tdi, td) {
                        // get column settings by field
                        let column = $.grep(columns, function (n, i) {
                            return $(td).data('field') === n.field;
                        })[0];
                        if (typeof column !== 'undefined') {
                            let value = $(td).text();
                            // enable column subtable toggle
                            if (typeof column.subtable !== 'undefined' && column.subtable) {
                                // check if subtable toggle exist
                                if ($(td).find('.' + pfx + 'datatable__toggle-subtable').length > 0) return;
                                // append subtable toggle
                                $(td).html($('<a/>').addClass(pfx + 'datatable__toggle-subtable').attr('href', '#').attr('data-value', value).attr('title', Plugin.getOption('detail.title')).on('click', toggleSubTable).append($('<i/>').css('width', $(td).data('width')).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'))));
                            }
                        }
                    });
                });

                // $(datatable.tableHead).find('.'+pfx+'-datatable__row').first()
            },

            /**
             * 从响应数据中获取数据
             */
            dataMapCallback: function (raw) {
                let dataSet = raw;
                // 是否自定义map
                if (typeof Plugin.getOption('data.source.read.map') === 'function') {
                    return Plugin.getOption('data.source.read.map')(raw);
                } else {
                    // 默认获取['data']
                    if (typeof raw !== 'undefined' && typeof raw.data !== 'undefined') {
                        dataSet = raw.data;
                    }
                }
                return dataSet;
            },

            isSpinning: false,
            /**
             * 打开/关闭 BlockUI 等待提示
             * @param block
             * @param target
             */
            spinnerCallback: function (block, target) {
                if (typeof target === 'undefined') target = datatable;
                // 获取遮罩设置
                let spinnerOptions = Plugin.getOption('layout.spinner');
                // spinner 被禁用
                if (typeof spinnerOptions === 'undefined' || !spinnerOptions) {
                    return;
                }
                if (block) {
                    if (!Plugin.isSpinning) {
                        if (typeof spinnerOptions.message !== 'undefined' && spinnerOptions.message === true) {
                            // 使用默认提示文字
                            spinnerOptions.message = Plugin.getOption('translate.records.processing');
                        }
                        Plugin.isSpinning = true;
                        if (typeof app !== 'undefined') {
                            app.block(target, spinnerOptions);
                        }
                    }
                } else {
                    Plugin.isSpinning = false;
                    if (typeof app !== 'undefined') {
                        app.unblock(target);
                    }
                }
            },

            /**
             * 默认排序回调函数
             * @param data {array} 数据
             * @param sort {string} asc|desc 排序方式
             * @param column {object} 排序的列
             * @returns {*|Array.<T>|{sort, field}|{asc, desc}}
             */
            sortCallback: function (data, sort, column) {
                let type = column['type'] || 'string';
                let format = column['format'] || '';
                let field = column['field'];

                return $(data).sort(function (a, b) {
                    let aField = a[field];
                    let bField = b[field];

                    switch (type) {
                        case 'date':
                            if (typeof moment === 'undefined') {
                                throw new Error('Moment.js is required.');
                            }
                            let diff = moment(aField, format).diff(moment(bField, format));
                            if (sort === 'asc') {
                                return diff > 0 ? 1 : diff < 0 ? -1 : 0;
                            } else {
                                return diff < 0 ? 1 : diff > 0 ? -1 : 0;
                            }
                            break;

                        case 'number':
                            if (isNaN(parseFloat(aField)) && aField != null) {
                                aField = Number(aField.replace(/[^0-9\.-]+/g, ''));
                            }
                            if (isNaN(parseFloat(bField)) && bField != null) {
                                bField = Number(bField.replace(/[^0-9\.-]+/g, ''));
                            }
                            aField = parseFloat(aField);
                            bField = parseFloat(bField);
                            if (sort === 'asc') {
                                return aField > bField ? 1 : aField < bField ? -1 : 0;
                            } else {
                                return aField < bField ? 1 : aField > bField ? -1 : 0;
                            }
                            break;

                        case 'string':
                        default:
                            if (sort === 'asc') {
                                return aField > bField ? 1 : aField < bField ? -1 : 0;
                            } else {
                                return aField < bField ? 1 : aField > bField ? -1 : 0;
                            }
                            break;
                    }
                });
            },

            /**
             * 日志
             *
             * @param text {object} 文字
             * @param obj {object}
             */
            log: function (text, obj) {
                if (typeof obj === 'undefined') obj = '';
                if (datatable.debug) {
                    console.log(text, obj);
                }
            },

            /**
             *  Auto hide columnds overflow in row
             *  自动隐藏溢出列
             */
            autoHide: function () {
                let hiddenExist = false;
                // force hide enabled
                let hidDefault = $(datatable.table).find('[data-autohide-enabled]');
                if (hidDefault.length) {
                    hiddenExist = true;
                    hidDefault.hide();
                }

                let toggleHiddenColumns = function (e) {
                    e.preventDefault();

                    let row = $(this).closest('.' + pfx + 'datatable__row');
                    let detailRow = $(row).next();

                    if (!$(detailRow).hasClass(pfx + 'datatable__row-detail')) {
                        $(this).find('i').removeClass(Plugin.getOption('layout.icons.rowDetail.collapse')).addClass(Plugin.getOption('layout.icons.rowDetail.expand'));

                        let hiddenCells = $(row).find('.' + pfx + 'datatable__cell:hidden');
                        let clonedCells = hiddenCells.clone().show();

                        detailRow = $('<tr/>').addClass(pfx + 'datatable__row-detail').insertAfter(row);
                        let detailRowTd = $('<td/>').addClass(pfx + 'datatable__detail').attr('colspan', Plugin.getTotalColumns()).appendTo(detailRow);

                        let detailSubTable = $('<table/>');
                        $(clonedCells).each(function () {
                            let field = $(this).data('field');
                            let column = $.grep(options.columns, function (n, i) {
                                return field === n.field;
                            })[0];
                            $(detailSubTable).append($('<tr class="' + pfx + 'datatable__row"></tr>').append($('<td class="' + pfx + 'datatable__cell"></td>').append($('<span/>').append(column.title))).append(this));
                        });
                        $(detailRowTd).append(detailSubTable);

                    } else {
                        $(this).find('i').removeClass(Plugin.getOption('layout.icons.rowDetail.expand')).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'));
                        $(detailRow).remove();
                    }
                };

                setTimeout(function () {
                    $(datatable.table).find('.' + pfx + 'datatable__cell').show();
                    $(datatable.tableBody).each(function () {
                        let recursive = 0;
                        while ($(this)[0].offsetWidth < $(this)[0].scrollWidth && recursive < options.columns.length) {
                            $(datatable.table).find('.' + pfx + 'datatable__row').each(function (i) {
                                let cell = $(this).find('.' + pfx + 'datatable__cell:not(:hidden):not([data-autohide-disabled])').last();
                                $(cell).hide();
                                hiddenExist = true;
                            });
                            recursive++;
                        }
                    });

                    if (hiddenExist) {
                        // 改变列隐藏/显示
                        $(datatable.tableBody).find('.' + pfx + 'datatable__row').each(function () {
                            // if no toggle yet
                            if ($(this).find('.' + pfx + 'datatable__toggle-detail').length === 0) {
                                // add toggle
                                $(this).prepend($('<td/>').addClass(pfx + 'datatable__cell ' + pfx + 'datatable__toggle-detail').append($('<a/>').addClass(pfx + 'datatable__toggle-detail').attr('href', '').on('click', toggleHiddenColumns).append('<i class="' + Plugin.getOption('layout.icons.rowDetail.collapse') + '"></i>')));
                            }

                            // check if subtable toggle exist
                            if ($(datatable.tableHead).find('.' + pfx + 'datatable__toggle-detail').length === 0) {
                                // add empty column to the header and footer
                                $(datatable.tableHead).find('.' + pfx + 'datatable__row').first().prepend('<th class="' + pfx + 'datatable__cell ' + pfx + 'datatable__toggle-detail"><span></span></th>');
                                $(datatable.tableFoot).find('.' + pfx + 'datatable__row').first().prepend('<th class="' + pfx + 'datatable__cell ' + pfx + 'datatable__toggle-detail"><span></span></th>');
                            } else {
                                $(datatable.tableHead).find('.' + pfx + 'datatable__toggle-detail').find('span');
                            }
                        });
                    }
                });

                Plugin.adjustCellsWidth.call();
            },

            /**
             * 自动将服务器返回数据第一条作为表格标题
             */
            setAutoColumns: function () {
                if (Plugin.getOption('data.autoColumns')) {
                    $.each(datatable.dataSet[0], function (k, v) {
                        let found = $.grep(options.columns, function (n, i) {
                            return k === n.field;
                        });
                        if (found.length === 0) {
                            options.columns.push({field: k, title: k});
                        }
                    });
                    $(datatable.tableHead).find('.' + pfx + 'datatable__row').remove();
                    Plugin.setHeadTitle();
                    if (Plugin.getOption('layout.footer')) {
                        $(datatable.tableFoot).find('.' + pfx + 'datatable__row').remove();
                        Plugin.setHeadTitle(datatable.tableFoot);
                    }
                }
            },

            /********************
             ** 工具
             ********************/

            /**
             * 检查表格中是否有锁定列
             */
            isLocked: function () {
                let isLocked = Plugin.lockEnabledColumns();
                return isLocked.left.length > 0 || isLocked.right.length > 0;
            },

            /**
             * 获取用于宽度计算的元素的额外空间 (包括 padding, margin, border)
             * @param element
             * @returns {number}
             */
            getExtraSpace: function (element) {
                let padding = parseInt($(element).css('paddingRight')) +
                    parseInt($(element).css('paddingLeft'));
                let margin = parseInt($(element).css('marginRight')) +
                    parseInt($(element).css('marginLeft'));
                let border = Math.ceil(
                    $(element).css('border-right-width').replace('px', ''));
                return padding + margin + border;
            },

            /**
             * 将数组的数据插入{{}}模板占位符中
             * @param template {string} 模板
             * @param data {array} 数据
             * @returns {*}
             */
            dataPlaceholder: function (template, data) {
                let result = template;
                $.each(data, function (key, val) {
                    result = result.replace('{{' + key + '}}', val);
                });
                return result;
            },

            /**
             * 获取表格唯一id
             *
             * @param suffix {string} 后缀
             * @returns {*}
             */
            getTableId: function (suffix) {
                if (typeof suffix === 'undefined') suffix = '';
                let id = $(datatable).attr('id');
                if (typeof id === 'undefined') {
                    id = $(datatable).attr('class').split(' ')[0];
                }
                return id + suffix;
            },

            /**
             * 根据表格级别获取表格前缀
             */
            getTablePrefix: function (suffix) {
                if (typeof suffix !== 'undefined') suffix = '-' + suffix;
                return Plugin.getTableId() + '-' + Plugin.getDepth() + suffix;
            },

            /**
             * 获取当前表格在子表中的级别
             *
             * @returns {number}
             */
            getDepth: function () {
                let depth = 0;
                let table = datatable.table;
                do {
                    table = $(table).parents('.' + pfx + 'datatable__table');
                    depth++;
                } while ($(table).length > 0);
                return depth;
            },

            /**
             * 保存表格状态
             *
             * @param key {string} 关键字
             * @param value {object} 值
             */
            stateKeep: function (key, value) {
                key = Plugin.getTablePrefix(key);
                if (Plugin.getOption('data.saveState') === false) return;
                if (Plugin.getOption('data.saveState.webstorage') && localStorage) {
                    localStorage.setItem(key, JSON.stringify(value));
                }
                if (Plugin.getOption('data.saveState.cookie')) {
                    Cookies.set(key, JSON.stringify(value));
                }
            },

            /**
             * 获取表格状态
             *
             * @param key {string} 关键字
             */
            stateGet: function (key, defValue) {
                key = Plugin.getTablePrefix(key);
                if (Plugin.getOption('data.saveState') === false) return;
                let value = null;
                if (Plugin.getOption('data.saveState.webstorage') && localStorage) {
                    value = localStorage.getItem(key);
                } else {
                    value = Cookies.get(key);
                }
                if (typeof value !== 'undefined' && value !== null) {
                    return JSON.parse(value);
                }
            },

            /**
             * 更新cookies/localStorage中的状态
             *
             * @param key {string} 关键字
             * @param value {object} 值
             */
            stateUpdate: function (key, value) {
                let ori = Plugin.stateGet(key);
                if (typeof ori === 'undefined' || ori === null) ori = {};
                Plugin.stateKeep(key, $.extend({}, ori, value));
            },

            /**
             * 移除指定cookies/localStorage
             *
             * @param key {string} key
             */
            stateRemove: function (key) {
                key = Plugin.getTablePrefix(key);
                if (localStorage) {
                    localStorage.removeItem(key);
                }
                Cookies.remove(key);
            },

            /**
             * 获取列数量
             */
            getTotalColumns: function (tablePart) {
                if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
                return $(tablePart).find('.' + pfx + 'datatable__row').first().find('.' + pfx + 'datatable__cell').length;
            },

            /**
             * 获取表格中的一行
             *
             * @param tablePart {string} 表格选择器
             * @param row {int} 行号 从1开始
             * @param tdOnly {boolean} 只返回td
             * @returns {*}
             */
            getOneRow: function (tablePart, row, tdOnly) {
                if (typeof tdOnly === 'undefined') tdOnly = true;
                // get list of <tr>
                let result = $(tablePart).find('.' + pfx + 'datatable__row:not(.' + pfx + 'datatable__row-detail):nth-child(' + row + ')');
                if (tdOnly) {
                    // get list of <td> or <th>
                    result = result.find('.' + pfx + 'datatable__cell');
                }
                return result;
            },

            /**
             * Sort table row at HTML level by column index.
             * todo; Not in use.
             * @param header Header sort clicked
             * @param sort asc|desc. Optional. Default asc
             * @param int Boolean. Optional. Comparison value parse to integer.
             *     Default false
             */
            sortColumn: function (header, sort, int) {
                if (typeof sort === 'undefined') sort = 'asc'; // desc
                if (typeof int === 'undefined') int = false;

                let column = $(header).index();
                let rows = $(datatable.tableBody).find('.' + pfx + 'datatable__row');
                let hIndex = $(header).closest('.' + pfx + 'datatable__lock').index();
                if (hIndex !== -1) {
                    rows = $(datatable.tableBody).find('.' + pfx + 'datatable__lock:nth-child(' + (hIndex + 1) + ')').find('.' + pfx + 'datatable__row');
                }

                let container = $(rows).parent();
                $(rows).sort(function (a, b) {
                    let tda = $(a).find('td:nth-child(' + column + ')').text();
                    let tdb = $(b).find('td:nth-child(' + column + ')').text();

                    if (int) {
                        // useful for integer type sorting
                        tda = parseInt(tda);
                        tdb = parseInt(tdb);
                    }

                    if (sort === 'asc') {
                        return tda > tdb ? 1 : tda < tdb ? -1 : 0;
                    } else {
                        return tda < tdb ? 1 : tda > tdb ? -1 : 0;
                    }
                }).appendTo(container);
            },

            /**
             * 排序
             */
            sorting: function () {
                let sortObj = {
                    init: function () {
                        if (options.sortable) {
                            $(datatable.tableHead).find('.' + pfx + 'datatable__cell:not(.' + pfx + 'datatable__cell--check)').addClass(pfx + 'datatable__cell--sort').off('click').on('click', sortObj.sortClick);
                            // first init
                            sortObj.setIcon();
                        }
                    },
                    setIcon: function () {
                        let meta = Plugin.getDataSourceParam('sort');
                        if ($.isEmptyObject(meta)) return;

                        let column = Plugin.getColumnByField(meta.field);
                        // sort is disabled for this column
                        if (typeof column !== 'undefined' && typeof column.sortable !== 'undefined' && column.sortable === false) return;

                        // 获取head中的图标
                        let td = $(datatable.tableHead).find('.' + pfx + 'datatable__cell[data-field="' + meta.field + '"]').attr('data-sort', meta.sort);
                        let sorting = $(td).find('span');
                        let icon = $(sorting).find('i');

                        let icons = Plugin.getOption('layout.icons.sort');
                        // 更新图标; desc & asc
                        if ($(icon).length > 0) {
                            $(icon).removeAttr('class').addClass(icons[meta.sort]);
                        } else {
                            $(sorting).append($('<i/>').addClass(icons[meta.sort]));
                        }

                        // set sorted class to header on init
                        $(td).addClass(pfx + 'datatable__cell--sorted');
                    },
                    sortClick: function (e) {
                        let meta = Plugin.getDataSourceParam('sort');
                        let field = $(this).data('field');
                        let column = Plugin.getColumnByField(field);
                        // 如果该列已经禁用排序,移除排序按钮
                        if (typeof column.sortable !== 'undefined' && column.sortable === false) return;

                        // 设置header排序class
                        $(datatable.tableHead).find('th').removeClass(pfx + 'datatable__cell--sorted');
                        $(this).addClass(pfx + 'datatable__cell--sorted');
                        $(datatable.tableHead).find('.' + pfx + 'datatable__cell > span > i').remove();

                        if (options.sortable) {
                            Plugin.spinnerCallback(true);

                            let sort = 'desc';
                            if (Plugin.getObject('field', meta) === field) {
                                sort = Plugin.getObject('sort', meta);
                            }

                            // 排序方式
                            sort = typeof sort === 'undefined' || sort === 'desc'
                                ? 'asc'
                                : 'desc';

                            // 更新排序方式
                            meta = {field: field, sort: sort, sortField: column.sortField ? column.sortField : field};
                            Plugin.setDataSourceParam('sort', meta);

                            sortObj.setIcon();

                            setTimeout(function () {
                                Plugin.dataRender('sort');
                                $(datatable).trigger(pfx + 'datatable--on-sort', meta);
                            }, 300);
                        }
                    },
                };
                sortObj.init();
            },

            /**
             * 更新本地数据的 排序,过滤,分页
             * 在使用dataSet变量之前调用该方法
             *
             * @returns {*|null}
             */
            localDataUpdate: function () {
                let params = Plugin.getDataSourceParam();
                if (typeof datatable.originalDataSet === 'undefined') {
                    datatable.originalDataSet = datatable.dataSet;
                }

                let field = Plugin.getObject('sort.field', params);
                let sort = Plugin.getObject('sort.sort', params);
                let column = Plugin.getColumnByField(field);
                if (typeof column !== 'undefined' && Plugin.getOption('data.serverSorting') !== true) {
                    if (typeof column.sortCallback === 'function') {
                        datatable.dataSet = column.sortCallback(datatable.originalDataSet, sort, column);
                    } else {
                        datatable.dataSet = Plugin.sortCallback(datatable.originalDataSet, sort, column);
                    }
                } else {
                    datatable.dataSet = datatable.originalDataSet;
                }

                // 如果启用服务端分页,不用在本地过滤
                if (typeof params.query === 'object' && !Plugin.getOption('data.serverFiltering')) {
                    params.query = params.query || {};

                    let nestedSearch = function (obj) {
                        for (let field in obj) {
                            if (!obj.hasOwnProperty(field)) continue;
                            if (typeof obj[field] === 'string') {
                                if (obj[field].toLowerCase() === search || obj[field].toLowerCase().indexOf(search) !== -1) {
                                    return true;
                                }
                            } else if (typeof obj[field] === 'number') {
                                if (obj[field] === search) {
                                    return true;
                                }
                            } else if (typeof obj[field] === 'object') {
                                if (nestedSearch(obj[field])) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    };
                    // 获取查询条件
                    $(Plugin.getOption('search.input')).each(function (index, element) {
                        let search = $(element).val();
                        let key = Plugin.getGeneralSearchKey(element);
                        if (util.isNotBlank(key)) {
                            if (util.isNotBlank(search)) {
                                params.query[key] = search;
                            } else {
                                delete params.query[key];
                            }
                        }
                    });

                    // 移除数组中的空元素
                    $.each(params.query, function (k, v) {
                        if (v === '') {
                            delete params.query[k];
                        }
                    });

                    // 根据查询条件过滤
                    datatable.dataSet = Plugin.filterArray(datatable.dataSet, params.query);

                    // 重置数组index
                    datatable.dataSet = datatable.dataSet.filter(function () {
                        return true;
                    });
                }

                return datatable.dataSet;
            },

            /**
             * 过滤{key:value}数组
             *
             * @param list {array} 数组
             * @param args 查询条件
             * @param operator 关联(AND/OR/NOT)
             * @returns {*}
             */
            filterArray: function (list, args, operator) {
                if (typeof list !== 'object') {
                    return [];
                }

                if (typeof operator === 'undefined') operator = 'AND';

                if (typeof args !== 'object') {
                    return list;
                }

                operator = operator.toUpperCase();

                if ($.inArray(operator, ['AND', 'OR', 'NOT']) === -1) {
                    return [];
                }

                let count = Object.keys(args).length;
                let filtered = [];

                $.each(list, function (key, obj) {
                    let to_match = obj;

                    let matched = 0;
                    $.each(args, function (m_key, m_value) {
                        m_value = m_value instanceof Array ? m_value : [m_value];
                        let match_property = Plugin.getObject(m_key, to_match);
                        if (typeof match_property !== 'undefined' && match_property) {
                            let lhs = match_property.toString().toLowerCase();
                            m_value.forEach(function (item, index) {
                                if (item.toString().toLowerCase() === lhs || lhs.indexOf(item.toString().toLowerCase()) !== -1) {
                                    matched++;
                                }
                            });
                        }
                    });

                    if (('AND' === operator && matched == count) ||
                        ('OR' === operator && matched > 0) ||
                        ('NOT' === operator && 0 == matched)) {
                        filtered[key] = obj;
                    }
                });

                list = filtered;

                return list;
            },

            /**
             * 重置滚动条
             */
            resetScroll: function () {
                if (typeof options.detail === 'undefined' && Plugin.getDepth() === 1) {
                    $(datatable.table).find('.' + pfx + 'datatable__row').css('left', 0);
                    $(datatable.table).find('.' + pfx + 'datatable__lock').css('top', 0);
                    $(datatable.tableBody).scrollTop(0);
                }
            },

            /**
             * 根据列名获取列
             *
             * @param field 列名
             * @returns {object}
             */
            getColumnByField: function (field) {
                if (typeof field === 'undefined') return;
                let result;
                $.each(options.columns, function (i, column) {
                    if (field === column.field) {
                        result = column;
                        return false;
                    }
                });
                return result;
            },

            /**
             * 获取默认排序列
             */
            getDefaultSortColumn: function () {
                let result;
                $.each(options.columns, function (i, column) {
                    if (typeof column.sortable !== 'undefined'
                        && $.inArray(column.sortable, ['asc', 'desc']) !== -1) {
                        result = {sort: column.sortable, field: column.field, sortField: column.sortField ? column.sortField : column.field};
                        return false;
                    }
                });
                return result;
            },

            /**
             * 获取隐藏元素属性
             * @param element {object} 元素
             * @param includeMargin {boolean} 包括margin
             * @returns {{width: number, height: number, innerWidth: number, innerHeight: number, outerWidth: number, outerHeight: number}}
             */
            getHiddenDimensions: function (element, includeMargin) {
                let props = {
                        position: 'absolute',
                        visibility: 'hidden',
                        display: 'block'
                    },
                    dim = {
                        width: 0,
                        height: 0,
                        innerWidth: 0,
                        innerHeight: 0,
                        outerWidth: 0,
                        outerHeight: 0
                    },
                    hiddenParents = $(element).parents().addBack().not(':visible');
                includeMargin = (typeof includeMargin === 'boolean')
                    ? includeMargin
                    : false;

                let oldProps = [];
                hiddenParents.each(function () {
                    let old = {};

                    for (let name in props) {
                        old[name] = this.style[name];
                        this.style[name] = props[name];
                    }

                    oldProps.push(old);
                });

                dim.width = $(element).width();
                dim.outerWidth = $(element).outerWidth(includeMargin);
                dim.innerWidth = $(element).innerWidth();
                dim.height = $(element).height();
                dim.innerHeight = $(element).innerHeight();
                dim.outerHeight = $(element).outerHeight(includeMargin);

                hiddenParents.each(function (i) {
                    let old = oldProps[i];
                    for (let name in props) {
                        this.style[name] = old[name];
                    }
                });

                return dim;
            },
            /**
             * 获取查询条件元素的name/id
             *
             * @returns {*}
             */
            getGeneralSearchKey: function () {
                let searchInput = $(Plugin.getOption('search.input'));
                return $(searchInput).prop('name') || $(searchInput).prop('id');
            },

            /**
             * 根据路径获取对象
             *
             * @param path {string} 属性路径
             * @param object {object}
             * @returns {*}
             */
            getObject: function (path, object) {
                return path.split('.').reduce(function (obj, i) {
                    return obj !== null && typeof obj[i] !== 'undefined' ? obj[i] : null;
                }, object);
            },

            /**
             * Extend object
             * @param obj
             * @param path
             * @param value
             * @returns {*}
             */
            extendObj: function (obj, path, value) {
                let levels = path.split('.'),
                    i = 0;

                function createLevel(child) {
                    let name = levels[i++];
                    if (typeof child[name] !== 'undefined' && child[name] !== null) {
                        if (typeof child[name] !== 'object' &&
                            typeof child[name] !== 'function') {
                            child[name] = {};
                        }
                    } else {
                        child[name] = {};
                    }
                    if (i === levels.length) {
                        child[name] = value;
                    } else {
                        createLevel(child[name]);
                    }
                }

                createLevel(obj);
                return obj;
            },

            rowEvenOdd: function () {
                // row even class
                $(datatable.tableBody).find('.' + pfx + 'datatable__row').removeClass(pfx + 'datatable__row--even');
                if ($(datatable.wrap).hasClass(pfx + 'datatable--subtable')) {
                    $(datatable.tableBody).find('.' + pfx + 'datatable__row:not(.' + pfx + 'datatable__row-detail):even').addClass(pfx + 'datatable__row--even');
                } else {
                    $(datatable.tableBody).find('.' + pfx + 'datatable__row:nth-child(even)').addClass(pfx + 'datatable__row--even');
                }
            },

            /********************
             ** 公开方法
             ********************/

            // 延迟时间
            timer: 0,

            /**
             * 重绘
             * @returns {jQuery}
             */
            redraw: function () {
                Plugin.adjustCellsWidth.call();
                if (Plugin.isLocked()) {
                    // fix hiding cell width issue
                    Plugin.scrollbar();
                    Plugin.resetScroll();
                    Plugin.adjustCellsHeight.call();
                }
                Plugin.adjustLockContainer.call();
                Plugin.initHeight.call();
                if(datatable.scrollbarElement){
                    let ps = $(datatable.scrollbarElement).data('ps');
                    if(ps){
                        ps.update();
                    }
                }
                return datatable;
            },

            /**
             * 重新加载数据
             *
             * @returns {jQuery}
             */
            load: function () {
                Plugin.reload();
                return datatable;
            },

            /**
             * 重新加载数据
             *
             * @returns {jQuery}
             */
            reload: function () {
                let delay = (function () {
                    return function (callback, ms) {
                        clearTimeout(Plugin.timer);
                        Plugin.timer = setTimeout(callback, ms);
                    };
                })();
                delay(function () {
                    // local only. remote pagination will skip this block
                    if (!options.data.serverFiltering) {
                        Plugin.localDataUpdate();
                    }
                    Plugin.dataRender();
                    $(datatable).trigger(pfx + 'datatable--on-reloaded');
                }, Plugin.getOption('search.delay'));
                return datatable;
            },

            /**
             * 根据数据id获取数据
             *
             * @param id {string} 数据id
             * @returns {jQuery}
             */
            getRecord: function (id) {
                if (typeof datatable.tableBody === 'undefined') datatable.tableBody = $(datatable.table).children('tbody');
                $(datatable.tableBody).find('.' + pfx + 'datatable__cell:first-child').each(function (i, cell) {
                    if (id == $(cell).text()) {
                        let rowNumber = $(cell).closest('.' + pfx + 'datatable__row').index() + 1;
                        datatable.API.record = datatable.API.value = Plugin.getOneRow(datatable.tableBody, rowNumber);
                        return datatable;
                    }
                });
                return datatable;
            },

            /**
             * 根据列名获取列
             *
             * @param columnName {string} 列名
             * @returns {jQuery}
             */
            getColumn: function (columnName) {
                datatable.API.value = $(datatable.API.record).find('[data-field="' + columnName + '"]');
                return datatable;
            },

            /**
             * 销毁并还原表格
             *
             * @returns {jQuery}
             */
            destroy: function () {
                $(datatable).parent().find('.' + pfx + 'datatable__pager').remove();
                let initialDatatable = $(datatable.initialDatatable).addClass(pfx + 'datatable--destroyed').show();
                $(datatable).replaceWith(initialDatatable);
                datatable = initialDatatable;
                $(datatable).trigger(pfx + 'datatable--on-destroy');
                Plugin.isInit = false;
                initialDatatable = null;
                return initialDatatable;
            },

            /**
             * 根据指定列排序
             *
             * @param field {string} 列名
             * @param sort {string} asc/desc 排序方式 (默认:asc)
             */
            sort: function (field, sort) {
                // toggle sort
                sort = typeof sort === 'undefined' ? 'asc' : sort;

                Plugin.spinnerCallback(true);
                let column = Plugin.getColumnByField(field);

                // 更新排序方式
                let meta = {field: field, sort: sort, sortField: column.sortField ? column.sortField : field};
                Plugin.setDataSourceParam('sort', meta);

                setTimeout(function () {
                    Plugin.dataRender('sort');
                    $(datatable).trigger(pfx + 'datatable--on-sort', meta);
                    $(datatable.tableHead).find('.' + pfx + 'datatable__cell > span > i').remove();
                }, 300);

                return datatable;
            },

            /**
             * 获取当前选中数据值
             *
             * @returns {array}
             */
            getValue: function () {
                let ids = [];
                let selectedRecords = datatable.getSelectedRecords();
                if(datatable.hasClass(pfx + 'datatable--lock')){
                    selectedRecords = selectedRecords.filter('.' + pfx + 'datatable__lock--scroll > .' + pfx + 'datatable__row');
                }
                if (selectedRecords != null && selectedRecords.length > 0) {
                    for (let i = 0; i < selectedRecords.length; i++) {
                        let _id = $(selectedRecords[i]).data('id');
                        if (typeof _id !== 'undefined') {
                            ids.push(_id);
                        }
                    }
                }
                return ids;
            },

            /**
             * 根据CheckBox设置行选中
             *
             * @param cell {string|number|object} checkbox value / checkbox element
             */
            setActive: function (cell) {
                if (typeof cell === 'string' || typeof cell === 'number') {
                    // 根据CheckBox id
                    cell = $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"][value="' + cell + '"]');
                }

                $(cell).prop('checked', true);

                let ids = [];
                $(cell).each(function (i, td) {
                    // 查找选中行
                    let row = $(td).closest('tr').addClass(pfx + 'datatable__row--active');
                    let colIndex = $(row).index() + 1;

                    // 锁定的列
                    $(row).closest('tbody').find('tr:nth-child(' + colIndex + ')').not('.' + pfx + 'datatable__row-subtable').addClass(pfx + 'datatable__row--active');

                    let id = $(td).attr('value');
                    if (typeof id !== 'undefined') {
                        ids.push(id);
                    }
                });

                $(datatable).trigger(pfx + 'datatable--on-check', [ids]);
            },

            /**
             * 根据CheckBox设置行不选中
             *
             * @param cell {string|number|object} checkbox value / checkbox element
             */
            setInactive: function (cell) {
                if (typeof cell === 'string' || typeof cell === 'number') {
                    // 根据CheckBox id
                    cell = $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"][value="' + cell + '"]');
                }

                $(cell).prop('checked', false);

                let ids = [];
                $(cell).each(function (i, td) {
                    // 获取选中行
                    let row = $(td).closest('tr').removeClass(pfx + 'datatable__row--active');
                    let colIndex = $(row).index() + 1;

                    // 锁定列
                    $(row).closest('tbody').find('tr:nth-child(' + colIndex + ')').not('.' + pfx + 'datatable__row-subtable').removeClass(pfx + 'datatable__row--active');

                    let id = $(td).attr('value');
                    if (typeof id !== 'undefined') {
                        ids.push(id);
                    }
                });

                $(datatable).trigger(pfx + 'datatable--on-uncheck', [ids]);
            },

            /**
             * Set all checkboxes active or inactive
             * @param active
             */
            setActiveAll: function (active) {
                let checkboxes = $(datatable.table).find('> tbody, > thead').find('tr').not('.' + pfx + 'datatable__row-subtable').find('.' + pfx + 'datatable__cell--check [type="checkbox"]');
                if (active) {
                    Plugin.setActive(checkboxes);
                } else {
                    Plugin.setInactive(checkboxes);
                }
            },

            /**
             * 获取选中记录
             * @returns {null}
             */
            getSelectedRecords: function () {
                datatable.API.record = datatable.rows('.' + pfx + 'datatable__row--active').nodes();
                return datatable.API.record;
            },

            /**
             * 获取选项
             *
             * @param path 属性路径
             * @returns {object} 选项
             */
            getOption: function (path) {
                return Plugin.getObject(path, options);
            },

            /**
             * 设置选项
             *
             * @param path 属性路径
             * @param object {object} 选项
             */
            setOption: function (path, object) {
                options = Plugin.extendObj(options, path, object);
            },

            /**
             * 查询数据
             *
             * @param value 值
             * @param columns {array/string} 列名称
             */
            search: function (value, columns) {
                if (typeof columns !== 'undefined') columns = $.makeArray(columns);
                let delay = (function () {
                    return function (callback, ms) {
                        clearTimeout(Plugin.timer);
                        Plugin.timer = setTimeout(callback, ms);
                    };
                })();

                delay(function () {
                    // 获取查询条件
                    let query = Plugin.getDataSourceQuery();

                    // 如果列名为空
                    if (typeof columns === 'undefined' && typeof value !== 'undefined') {
                        let key = Plugin.getGeneralSearchKey();
                        query[key] = value;
                    }

                    // 根据指定列名搜索,支持多列名
                    if (typeof columns === 'object') {
                        $.each(columns, function (k, column) {
                            query[column] = value;
                        });
                        // 移除空值
                        $.each(query, function (k, v) {
                            if (v === '' || $.isEmptyObject(v)) {
                                delete query[k];
                            }
                        });
                    }

                    Plugin.setDataSourceQuery(query);

                    // 如果是本地筛选
                    if (!options.data.serverFiltering) {
                        Plugin.localDataUpdate();
                    }
                    Plugin.dataRender('search');
                }, Plugin.getOption('search.delay'));
            },

            /**
             * 设置数据源中对象
             *
             * @param param {string} 对象名称
             * @param value {object} 值
             */
            setDataSourceParam: function (param, value) {
                datatable.API.params = $.extend({}, {
                    page: {current: 1, size: Plugin.getOption('data.pageSize')},
                    sort: Plugin.getDefaultSortColumn(),
                    query: {}
                }, datatable.API.params, Plugin.stateGet(Plugin.stateId));

                datatable.API.params = Plugin.extendObj(datatable.API.params, param, value);

                Plugin.stateKeep(Plugin.stateId, datatable.API.params);
            },

            /**
             * 获取数据源中指定对象
             *
             * @param param {string|null} 对象名称
             */
            getDataSourceParam: function (param) {
                datatable.API.params = $.extend({}, {
                    page: {current: 1, size: Plugin.getOption('data.pageSize')},
                    sort: Plugin.getDefaultSortColumn(),
                    query: {}
                }, datatable.API.params, Plugin.stateGet(Plugin.stateId));

                if (typeof param === 'string') {
                    return Plugin.getObject(param, datatable.API.params);
                }

                return datatable.API.params;
            },

            /**
             * 获取查询条件
             * 示例: datatable.getDataSourceParam('query');
             *
             * @returns {*}
             */
            getDataSourceQuery: function () {
                return Plugin.getDataSourceParam('query') || {};
            },

            /**
             * 设置查询条件
             * 示例: datatable.setDataSourceParam('query', query);
             *
             * @param query {string} 查询条件
             */
            setDataSourceQuery: function (query) {
                Plugin.setDataSourceParam('query', query);
            },

            /**
             * 获取当前页
             *
             * @returns {number}
             */
            getCurrentPage: function () {
                return $(datatable.table).siblings('.' + pfx + 'datatable__pager').last().find('.' + pfx + 'datatable__pager-nav').find('.' + pfx + 'datatable__pager-link.' + pfx + 'datatable__pager-link--active').data('page') || 1;
            },

            /**
             * 获取当前选中页大小(默认10)
             *
             * @returns {*|number}
             */
            getPageSize: function () {
                return $(datatable.table).siblings('.' + pfx + 'datatable__pager').last().find('select.' + pfx + 'datatable__pager-size').val() || 10;
            },

            /**
             * 获取工具条
             */
            getTotalRows: function () {
                return datatable.API.params.pagination.total;
            },

            /**
             * 获取表格所有数据
             *
             * @returns {*|null|Array}
             */
            getDataSet: function () {
                return datatable.originalDataSet;
            },

            /**
             * @deprecated in v5.0.6
             * Hide column by column's field name
             * 根据列名隐藏列
             * @param fieldName
             */
            hideColumn: function (fieldName) {
                // add hide option for this column
                $.map(options.columns, function (column) {
                    if (fieldName === column.field) {
                        column.responsive = {hidden: 'xl'};
                    }
                    return column;
                });
                // hide current displayed column
                let tds = $.grep($(datatable.table).find('.' + pfx + 'datatable__cell'), function (n, i) {
                    return fieldName === $(n).data('field');
                });
                $(tds).hide();
            },

            /**
             * @deprecated in v5.0.6
             * Show column by column's field name
             * 根据列名显示列
             * @param fieldName
             */
            showColumn: function (fieldName) {
                // add hide option for this column
                $.map(options.columns, function (column) {
                    if (fieldName === column.field) {
                        delete column.responsive;
                    }
                    return column;
                });
                // hide current displayed column
                let tds = $.grep($(datatable.table).find('.' + pfx + 'datatable__cell'), function (n, i) {
                    return fieldName === $(n).data('field');
                });
                $(tds).show();
            },

            nodeTr: [],
            nodeTd: [],
            nodeCols: [],
            recentNode: [],

            table: function () {
                if (typeof datatable.table !== 'undefined') {
                    return datatable.table;
                }
            },

            /**
             * 根据选择器查找第一行
             *
             * @param selector {string} 选择器
             * @returns {jQuery}
             */
            row: function (selector) {
                Plugin.rows(selector);
                Plugin.nodeTr = Plugin.recentNode = $(Plugin.nodeTr).first();
                return datatable;
            },

            /**
             *
             * 根据选择器查找多行
             *
             * @param selector {string} 选择器
             * @returns {jQuery}
             */
            rows: function (selector) {
                Plugin.nodeTr = Plugin.recentNode = $(datatable.tableBody).find(selector);
                return datatable;
            },

            /**
             * Select a single column from the table
             * 根据下标获取列
             *
             * @param index {int} 下标 从0开始
             * @returns {jQuery}
             */
            column: function (index) {
                Plugin.nodeCols = Plugin.recentNode = $(datatable.tableBody).find('.' + pfx + 'datatable__cell:nth-child(' + (index + 1) + ')');
                return datatable;
            },

            /**
             * Select multiple columns from the table
             * 根据条件查询多列
             *
             * @param selector {String} 条件
             * @returns {jQuery}
             */
            columns: function (selector) {
                let context = datatable.table;
                if (Plugin.nodeTr === Plugin.recentNode) {
                    context = Plugin.nodeTr;
                }
                let columns = $(context).find('.' + pfx + 'datatable__cell[data-field="' + selector + '"]');
                if (columns.length > 0) {
                    Plugin.nodeCols = Plugin.recentNode = columns;
                } else {
                    Plugin.nodeCols = Plugin.recentNode = $(context).find(selector).filter('.' + pfx + 'datatable__cell');
                }
                return datatable;
            },
            /**
             * 根据选择器查找第一个 cell
             *
             * @param selector {string} 选择器
             * @returns {jQuery}
             */
            cell: function (selector) {
                Plugin.cells(selector);
                Plugin.nodeTd = Plugin.recentNode = $(Plugin.nodeTd).first();
                return datatable;
            },
            /**
             * 根据选择器查找 cells
             *
             * @param selector {string} 选择器
             * @returns {jQuery}
             */
            cells: function (selector) {
                let cells = $(datatable.tableBody).find('.' + pfx + 'datatable__cell');
                if (typeof selector !== 'undefined') {
                    cells = $(cells).filter(selector);
                }
                Plugin.nodeTd = Plugin.recentNode = cells;
                return datatable;
            },

            /**
             * 删除选中的行
             *
             * @returns {jQuery}
             */
            remove: function () {
                if ($(Plugin.nodeTr.length) && Plugin.nodeTr === Plugin.recentNode) {
                    $(Plugin.nodeTr).remove();
                }
                Plugin.layoutUpdate();
                return datatable;
            },
            /**
             * 删除指定的行
             * @param rows {jquery} 要删除的行
             * @returns {jQuery}
             */
            removeRows: function (rows) {
                if (typeof rows !== 'undefined' && rows.length > 0) {
                    rows.remove();
                }
                Plugin.layoutUpdate();
                return datatable;
            },
            /**
             * 根据id删除指定行
             *
             * @param id id
             */
            removeById: function(id){
                let cell = null;
                if (typeof id === 'string' || typeof id === 'number') {
                    // 根据CheckBox id
                    cell = $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"][value="' + id + '"]');
                }
                // 查找行
                let row = $(cell).closest('tr');
                let colIndex = $(row).index() + 1;

                // 锁定的列
                $(row).closest('tbody').find('tr:nth-child(' + colIndex + ')').not('.' + pfx + 'datatable__row-subtable').remove();
                row.remove();
            },

            /**
             * 显示或隐藏行或列
             *
             * @param bool {boolean} 显示/隐藏
             */
            visible: function (bool) {
                if ($(Plugin.recentNode.length)) {
                    let locked = Plugin.lockEnabledColumns();
                    if (Plugin.recentNode === Plugin.nodeCols) {
                        let index = Plugin.recentNode.index();

                        if (Plugin.isLocked()) {
                            let scrollColumns = $(Plugin.recentNode).closest('.' + pfx + 'datatable__lock--scroll').length;
                            if (scrollColumns) {
                                // is at center of scrollable area
                                index += locked.left.length + 1;
                            } else if ($(Plugin.recentNode).closest('.' + pfx + 'datatable__lock--right').length) {
                                // is at the right locked table
                                index += locked.left.length + scrollColumns + 1;
                            }
                        }
                    }

                    if (bool) {
                        if (Plugin.recentNode === Plugin.nodeCols) {
                            delete options.columns[index].responsive;
                        }
                        $(Plugin.recentNode).show();
                    } else {
                        if (Plugin.recentNode === Plugin.nodeCols) {
                            Plugin.setOption('columns.' + index + '.responsive', {hidden: 'xl'});
                        }
                        $(Plugin.recentNode).hide();
                    }
                    Plugin.redraw();
                }
            },

            /**
             * 根据选择的行或者列获取DOM元素
             *
             * @returns {Array}
             */
            nodes: function () {
                return Plugin.recentNode;
            },

            /**
             * 获取datatable
             *
             * @returns {jQuery}
             */
            dataset: function () {
                return datatable;
            },

            /**
             * 跳转到指定页
             * @param page {number} 页码
             */
            gotoPage: function (page) {
                Plugin.pagingObject.openPage(page);
            },
            /**
             * 新增一行数据
             */
            addRow: function () {
                let colLength = options.columns.length;
                let tr = document.createElement('tr');
                $(tr).addClass(pfx + 'datatable__row');
                for (let i = 0; i < colLength; i++) {
                    let column = options.columns[i];
                    let element = Plugin.getColumnElement(column);
                    let td = document.createElement('td');
                    td.setAttribute('data-field', column.field);
                    $(td).addClass(pfx + 'datatable__cell ' + (typeof column.class != 'undefined' ? column.class : ''));
                    td.appendChild(element);
                    tr.appendChild(td);
                }
                datatable.tableBody.append(tr);

                // 更新布局
                Plugin.setupDOM(datatable.table);
                Plugin.setupCellField([datatable.tableBody]);
                Plugin.layoutUpdate();

                if (datatable.hasClass( pfx + 'datatable--error')) {
                    datatable.removeClass(pfx + 'datatable--error');
                    datatable.find('span.' + pfx + 'datatable--error').remove();
                }
                // 滚动到新增行
                datatable.tableBody.scrollTop = datatable.tableBody.scrollHeight;
                $(tr).find('.table-actions').each(function () {
                    app.initTooltip($(this));
                });
            },
            /**
             * 获取编辑表格列元素
             *
             * @param column {object} 列配置
             * @param defaultVal {string} 默认值
             * @returns {HTMLSpanElement | HTMLInputElement | HTMLSelectElement | HTMLButtonElement}
             */
            getColumnElement: function (column, defaultVal) {
                let element;
                if (typeof column.edit !== 'undefined') {
                    switch (column.edit.tag) {
                        case 'input':
                            element = document.createElement('input');
                            element.setAttribute('type', column.edit.type);
                            $(element).addClass('form-control form-control-sm ' + (column.edit.class ? column.edit.class : ''));
                            if (typeof defaultVal !== 'undefined') {
                                element.value = defaultVal;
                            }
                            break;
                        case 'select':
                            element = document.createElement('select');
                            $(element).addClass('form-control form-control-sm ' + (column.edit.class ? column.edit.class : ''));
                            if (typeof column.edit.option !== 'undefined') {
                                if (typeof defaultVal === 'undefined') {
                                    defaultVal = column.edit.default;
                                }
                                for (let key in column.edit.option) {
                                    let opt = document.createElement('option');
                                    opt.setAttribute('value', column.edit.option[key].code);
                                    if (column.edit.option[key].code === defaultVal) {
                                        opt.setAttribute('selected', 'true');
                                    }
                                    opt.innerText = column.edit.option[key].name;
                                    element.appendChild(opt);
                                }
                            }
                            break;
                        case 'button':
                            element = document.createElement('button');
                            element.setAttribute('type', 'button');
                            element.setAttribute('title', column.edit.title);
                            element.innerHTML = column.edit.text;
                            $(element).addClass(tool.ACTIONS_SUCCESS);
                            if (typeof column.edit.click == 'function') {
                                element.onclick = function () {
                                    column.edit.click($(this).parents('.' + pfx + 'datatable__row'), $(this).parents('.' + pfx + 'datatable__row').find('input, select').serializeArray());
                                }
                            }
                            break;
                        default:
                            element = document.createElement('input');
                            element.setAttribute('type', 'text');
                            $(element).addClass('form-control form-control-sm ' + (column.edit.class ? column.edit.class : ''));
                            if (typeof defaultVal !== 'undefined') {
                                element.value = defaultVal;
                            }
                    }
                    element.setAttribute('name', column.field);
                    $(element).addClass(column.edit.classes);
                } else {
                    element = document.createElement('span');
                    element.innerText = '#';
                }
                return element;
            },
            /**
             * 编辑行
             *
             * @param element {object} 编辑按钮对象
             */
            editRow: function (element) {
                let colLength = options.columns.length;
                let tr = $(element).parents('.' + pfx + 'datatable__row');
                let data = datatable.dataSet[Number(tr.data('row'))];
                for (let i = 0; i < colLength; i++) {
                    let column = options.columns[i];
                    element = Plugin.getColumnElement(column, data[column.field]);
                    let td = tr.find('td:eq(' + i + ')');
                    if (td.hasClass('e-datatable__cell--check')) {
                        td.find('span').html(element);
                    } else {
                        td.find('span').html(element);
                    }
                }
                tr.find('.table-actions').each(function () {
                    app.initTooltip($(this));
                });
            },
            /**
             * 恢复默认排序
             */
            recoveryDefaultOrder: function () {
                $(datatable.tableHead).find('th').removeClass(pfx + 'datatable__cell--sorted');
                $(datatable.tableHead).find('.' + pfx + 'datatable__cell > span > i').remove();
                Plugin.setDataSourceParam('sort', Plugin.getDefaultSortColumn());
                datatable.reload();
            },
            /**
             * 恢复默认页大小
             */
            recoveryDefaultPageSize: function () {
                Plugin.setDataSourceParam('page', {current: 1, size: Plugin.getOption('data.pageSize')});
                datatable.reload();
            },
            /**
             * 全部恢复全部
             *
             * @returns {*}
             */
            recoveryDefaultAll: function () {
                $(datatable.tableHead).find('th').removeClass(pfx + 'datatable__cell--sorted');
                $(datatable.tableHead).find('.' + pfx + 'datatable__cell > span > i').remove();
                Plugin.setDataSourceParam('sort', Plugin.getDefaultSortColumn());
                Plugin.setDataSourceParam('page', {current: 1, size: Plugin.getOption('data.pageSize')});
                datatable.reload();
            }
        };

        /**
         * Public API methods can be used directly by datatable
         */
        $.each(Plugin, function (funcName, func) {
            datatable[funcName] = func;
        });

        // 初始化插件
        if (typeof options !== 'undefined') {
            if (typeof options === 'string') {
                let method = options;
                datatable = $(this).data(pluginName);
                if (typeof datatable !== 'undefined') {
                    options = datatable.options;
                    Plugin[method].apply(this, Array.prototype.slice.call(arguments, 1));
                }
            } else {
                if (!datatable.data(pluginName) && !$(this).hasClass(pfx + 'datatable--loaded')) {
                    datatable.dataSet = null;
                    datatable.textAlign = {
                        left: pfx + 'datatable__cell--left',
                        center: pfx + 'datatable__cell--center',
                        right: pfx + 'datatable__cell--right'
                    };

                    // 合并默认与自定义option
                    options = $.extend(true, {}, $.fn[pluginName].defaults, options);

                    datatable.options = options;

                    // 初始化插件
                    Plugin.init.apply(this, [options]);

                    $(datatable.wrap).data(pluginName, datatable);
                }
            }
        } else {
            // 获取现有datatable
            datatable = $(this).data(pluginName);
            if (typeof datatable === 'undefined') {
                $.error(pluginName + ' not initialized');
            }
            options = datatable.options;
        }

        return datatable;
    };

    // 默认设置
    $.fn[pluginName].defaults = {
        // 数据源
        data: {
            type: 'local',
            source: null,
            pageSize: 10, // 默认页大小
            saveState: {
                // 使用cookie/webstorage 保存表格状态(分页, 筛选, 排序)
                cookie: false,
                webstorage: true
            },

            serverPaging: false, // 在服务器分页
            serverFiltering: false, // 在服务器进行数据过滤
            serverSorting: false, // 在服务器进行排序

            autoColumns: false, // 自动列
            attr: {
                rowProps: []
            }
        },

        // 布局
        layout: {
            theme: 'default', // 主题
            class: pfx + 'datatable--brand', // 容器 class
            scroll: false, // 启用禁用垂直/水平滚动条
            // 高度
            height: null,
            minHeight: 300, // 最小高度
            footer: false, // 显示/隐藏 footer
            header: true, // 显示/隐藏 header
            customScrollbar: true, // 自定义滚动条

            // 等待提示样式
            spinner: {
                overlayColor: '#000',
                opacity: 0,
                state: 'brand',
                message: true
            },

            // datatable 图标
            icons: {
                sort: {asc: 'flaticon2-arrow-up', desc: 'flaticon2-arrow-down'},
                pagination: {
                    next: 'flaticon2-next',
                    prev: 'flaticon2-back',
                    first: 'flaticon2-fast-back',
                    last: 'flaticon2-fast-next',
                    more: 'flaticon-more-1'
                },
                rowDetail: {expand: 'fa fa-caret-down', collapse: 'fa fa-caret-right'}
            }
        },

        // 列排序
        sortable: true,
        // 分页
        pagination: true,

        // 列配置
        columns: [],

        search: {
            // 通过keyup事件搜索
            onEnter: false,
            // 搜索框中提示文字
            input: null,
            // 搜索延迟 单位: 毫秒
            delay: 400
        },

        rows: {
            // callback
            callback: function () {
            },
            // 在拼接<tr>内容前调用
            beforeTemplate: function () {
            },
            // 在拼接<tr>内容后调用
            afterTemplate: function () {
            },
            // 如果列溢出,自动隐藏非锁定列
            autoHide: false
        },

        // 工具条
        toolbar: {
            // 布局
            layout: ['pagination', 'info'],

            // 设置工具条位于底部还是顶部
            placement: ['bottom'],  //'top', 'bottom'

            // 工具条选项
            items: {
                // 分页
                pagination: {
                    // 分页类型(default or scroll)
                    type: 'default',

                    // 不同设备下页码按钮显示数量
                    pages: {
                        desktop: {
                            layout: 'default',
                            pagesNumber: 5
                        },
                        tablet: {
                            layout: 'default',
                            pagesNumber: 3
                        },
                        mobile: {
                            layout: 'compact'
                        }
                    },

                    // 导航按钮
                    navigation: {
                        prev: true, // 上一页
                        next: true, // 下一页
                        first: true, // 第一页
                        last: true // 最后一页
                    },

                    // 页大小select
                    pageSizeSelect: []
                },

                // 记录信息
                info: true
            }
        },

        // 自定义插件提示文字
        translate: {
            records: {
                processing: '请稍候...',
                noRecords: '未查找到数据'
            },
            toolbar: {
                pagination: {
                    items: {
                        default: {
                            first: '第一页',
                            prev: '上一页',
                            next: '下一页',
                            last: '最后一页',
                            more: '更多页码',
                            input: '请输入页码',
                            select: '每页显示',
                            all: '全部'
                        },
                        info: '当前显示 {{start}} - {{end}} 共 {{total}} 条数据'
                    }
                }
            }
        },
        extensions: {}
    };
}(jQuery));

"use strict";
(function($) {

	let pluginName = 'EDataTable';
	let pfx = 'e-';

	$.fn[pluginName] = $.fn[pluginName] || {};

	/**
	 * @param datatable Main datatable plugin instance
	 * @param options Extension options
	 * @returns {*}
	 */
	$.fn[pluginName].checkbox = function(datatable, options) {
		let Extension = {
			selectedAllRows: false,
			selectedRows: [],
			unselectedRows: [],

			init: function() {
				if (Extension.selectorEnabled()) {
					// reset
					datatable.setDataSourceParam(options.vars.selectedAllRows, false);
					datatable.stateRemove('checkbox');

					// requestIds is not null
					if (options.vars.requestIds) {
						// request ids in response
						datatable.setDataSourceParam(options.vars.requestIds, true);
					}

					// remove selected checkbox on datatable reload
					$(datatable).on(pfx + 'datatable--on-reloaded', function() {
						datatable.stateRemove('checkbox');
						datatable.setDataSourceParam(options.vars.selectedAllRows, false);
						Extension.selectedAllRows = false;
						Extension.selectedRows = [];
						Extension.unselectedRows = [];
					});

					// select all on extension init
					Extension.selectedAllRows = datatable.getDataSourceParam(options.vars.selectedAllRows);

					$(datatable).on(pfx + 'datatable--on-layout-updated', function(e, args) {
						if (args.table != $(datatable.wrap).attr('id')) {
							return;
						}
						datatable.ready(function() {
							Extension.initVars();
							Extension.initEvent();
							Extension.initSelect();
						});
					});

					$(datatable).on(pfx + 'datatable--on-check', function(e, ids) {
						ids.forEach(function(id) {
							Extension.selectedRows.push(id);
							// // remove from unselected rows
							Extension.unselectedRows = Extension.remove(Extension.unselectedRows, id);
						});
						let storage = {};
						storage['selectedRows'] = $.unique(Extension.selectedRows);
						storage['unselectedRows'] = $.unique(Extension.unselectedRows);
						datatable.stateKeep('checkbox', storage);
					});
					$(datatable).on(pfx + 'datatable--on-uncheck', function(e, ids) {
						ids.forEach(function(id) {
							Extension.unselectedRows.push(id);
							// // remove from selected rows
							Extension.selectedRows = Extension.remove(Extension.selectedRows, id);
						});
						let storage = {};
						storage['selectedRows'] = $.unique(Extension.selectedRows);
						storage['unselectedRows'] = $.unique(Extension.unselectedRows);
						datatable.stateKeep('checkbox', storage);
					});
				}
			},

			/**
			 * Init checkbox clicks event
			 */
			initEvent: function() {
				// select all checkbox click
				$(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').click(function(e) {
					// clear selected and unselected rows
					Extension.selectedRows = Extension.unselectedRows = [];
					datatable.stateRemove('checkbox');

					// select all rows
					if ($(this).is(':checked')) {
						Extension.selectedAllRows = true;
					}
					else {
						Extension.selectedAllRows = false;
					}

					// local select all current page rows
					if (!options.vars.requestIds) {
						if ($(this).is(':checked')) {
							Extension.selectedRows = $.makeArray($(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"]').map(function(i, chk) {
								return $(chk).val();
							}));
						}
						let storage = {};
						storage['selectedRows'] = $.unique(Extension.selectedRows);
						datatable.stateKeep('checkbox', storage);
					}

					// keep selectedAllRows in datasource params
					datatable.setDataSourceParam(options.vars.selectedAllRows, Extension.selectedAllRows);

					$(datatable).trigger(pfx + 'datatable--on-click-checkbox', [$(this)]);
				});

				// single row checkbox click
				$(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"]').click(function(e) {
					let id = $(this).val();
					if ($(this).is(':checked')) {
						Extension.selectedRows.push(id);
						// remove from unselected rows
						Extension.unselectedRows = Extension.remove(Extension.unselectedRows, id);
					}
					else {
						Extension.unselectedRows.push(id);
						// remove from selected rows
						Extension.selectedRows = Extension.remove(Extension.selectedRows, id);
					}

					// local checkbox header check
					if (!options.vars.requestIds && Extension.selectedRows.length < 1) {
						// remove select all checkbox, if there is no checked checkbox left
						$(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').prop('checked', false);
					}

					let storage = {};
					storage['selectedRows'] = $.unique(Extension.selectedRows);
					storage['unselectedRows'] = $.unique(Extension.unselectedRows);
					datatable.stateKeep('checkbox', storage);

					$(datatable).trigger(pfx + 'datatable--on-click-checkbox', [$(this)]);
				});
			},

			initSelect: function() {
				// selected all rows from server
				if (Extension.selectedAllRows && options.vars.requestIds) {
					if (!datatable.hasClass(pfx + 'datatable--error')) {
						// set header select all checkbox checked
						$(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').prop('checked', true);
					}

					// set all checkbox in table body
					datatable.setActiveAll(true);

					// remove unselected rows
					Extension.unselectedRows.forEach(function(id) {
						datatable.setInactive(id);
					});

				}
				else {
					// single check for server and local
					Extension.selectedRows.forEach(function(id) {
						datatable.setActive(id);
					});

					// local checkbox; check if all checkboxes of currect page are checked
					if (!datatable.hasClass(pfx + 'datatable--error') && $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"]').not(':checked').length < 1) {
						// set header select all checkbox checked
						$(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').prop('checked', true);
					}
				}
			},

			/**
			 * Check if selector is enabled from options
			 */
			selectorEnabled: function() {
				return $.grep(datatable.options.columns, function(n, i) {
					return n.selector || false;
				})[0];
			},

			initVars: function() {
				// get single select/unselect from localstorage
				let storage = datatable.stateGet('checkbox');
				if (typeof storage !== 'undefined') {
					Extension.selectedRows = storage['selectedRows'] || [];
					Extension.unselectedRows = storage['unselectedRows'] || [];
				}
			},

			getSelectedId: function(path) {
				Extension.initVars();

				// server selected all rows
				if (Extension.selectedAllRows && options.vars.requestIds) {
					if (typeof path === 'undefined') {
						path = options.vars.rowIds;
					}

					// if selected all rows, return id from response meta
					let selectedAllRows = datatable.getObject(path, datatable.lastResponse) || [];

					if (selectedAllRows.length > 0) {
						// remove single unselected rows from selectedAllRows ids from server response emta
						Extension.unselectedRows.forEach(function(id) {
							selectedAllRows = Extension.remove(selectedAllRows, parseInt(id));
						});
					}
					return selectedAllRows;
				}

				// else return single checked selected rows
				return Extension.selectedRows;
			},

			remove: function(array, element) {
				return array.filter(function(e) {
					return e !== element;
				});
			},
		};

		// make the extension accessible from datatable init
		datatable.checkbox = function() {
			return Extension;
		};

		if (typeof options === 'object') {
			options = $.extend(true, {}, $.fn[pluginName].checkbox.default, options);
			Extension.init.apply(this, [options]);
		}

		return datatable;
	};

	$.fn[pluginName].checkbox.default = {
		vars: {
			// select all rows flag to be sent to the server
			selectedAllRows: 'selectedAllRows',
			// request id parameter's name
			requestIds: 'requestIds',
			// response path to all rows id
			rowIds: 'meta.rowIds',
		},
	};

}(jQuery));
"use strict";

let ELayout = function () {
    let $body = $('body');
    let $aside = $('#aside');
    let asideMenu = null;

    let asideMenuOffCanvas;

    /**
     * 移动端模式header-topbar显示 & 隐藏
     */
    let initMobileHeaderToggle = function () {
        $body.on('click', '#header-mobile-toggle', function () {
            let $headerMobileToggle = $(this);
            if ($headerMobileToggle.hasClass('active')) {
                // 隐藏
                $body.removeClass(cssPrefix + 'header-topbar--mobile-on');
                $headerMobileToggle.removeClass('active');
            } else {
                // 显示
                $body.addClass(cssPrefix + 'header-topbar--mobile-on');
                $headerMobileToggle.addClass('active');
            }
        });
    };

    /**
     * 移动端模式侧边显示 & 隐藏
     */
    let initMobileAsideToggler = function () {
        // 移动端模式菜单的显示 & 隐藏
        asideMenuOffCanvas = new EOffCanvas('#aside', {
            baseClass: 'e-aside',
            overlay: true,
            closeBy: '#ef_aside_close_btn',
            toggleBy: {
                // 用于切换显示 & 隐藏的按钮选择器
                target: '#header-mobile-aside-toggler',
                // 当显示时按钮上面添加的class
                state: 'active'
            }
        });
    };

    /**
     * pc模式切换菜单展开 & 收起
     */
    let initAsideToggler = function () {
        $body.on('click', '#aside-toggler', function () {
            let $asideToggler = $(this);
            if ($asideToggler.hasClass('active')) {
                $asideToggler.removeClass('active');
                // 还原
                $body.removeClass(cssPrefix + 'aside--minimize ' + cssPrefix + 'aside--minimize-hover');
            } else {
                $asideToggler.addClass('active');
                // 收起
                $body.addClass(cssPrefix + 'aside--minimize');
            }
        });

        // 当收起时，鼠标enter展开
        let insideTm;
        let outsideTm;

        $aside.mouseenter(function (e) {
            e.preventDefault();
            if (!EUtil.isInResponsiveRange('desktop')) {
                return;
            }
            // 清除消失定时器
            if (outsideTm) {
                clearTimeout(outsideTm);
                outsideTm = null;
            }

            insideTm = setTimeout(function () {
                if ($body.hasClass('e-aside--minimize')) {
                    $body.removeClass('e-aside--minimize');

                    // Minimizing class
                    $body.addClass('e-aside--minimizing');

                    EUtil.transitionEnd($body[0], function () {
                        $body.removeClass('e-aside--minimizing');
                    });

                    // Hover class
                    $body.addClass('e-aside--minimize-hover');
                    // asideMenu.scrollUpdate();
                    // asideMenu.scrollTop();
                }
            }, 50);
        });

        $aside.mouseleave(function (e) {
            e.preventDefault();

            if (!EUtil.isInResponsiveRange('desktop')) {
                return;
            }

            if (insideTm) {
                clearTimeout(insideTm);
                insideTm = null;
            }

            outsideTm = setTimeout(function () {
                if ($body.hasClass('e-aside--minimize-hover')) {
                    $body.removeClass('e-aside--minimize-hover');
                    $body.addClass('e-aside--minimize');

                    // Minimizing class
                    $body.addClass('e-aside--minimizing');
                    EUtil.transitionEnd($body[0], function () {
                        $body.removeClass('e-aside--minimizing');
                    });

                    // Hover class
                    // asideMenu.scrollUpdate();
                    // asideMenu.scrollTop();
                }
            }, 100);
        });
    };
    /**
     * 初始化侧边菜单
     */
    let initAsideMenu = function () {
        let $menu = $('#aside-menu');

        asideMenu = new EMenu('#aside-menu', {
            // 滚动条
            scroll: {
                height: function () {
                    let height;

                    if (EUtil.isInResponsiveRange('desktop')) {
                        height = parseInt(EUtil.getViewPort().height) - parseInt($('#aside-brand').height());
                    } else {
                        height = parseInt(EUtil.getViewPort().height);
                    }

                    height = height - (parseInt($menu.css('margin-top').replace('px', '')) + parseInt($menu.css('margin-bottom').replace('px', '')));

                    return height;
                }
            },
            accordion: {
                expandMultiple: false // 是否启用打开多个子菜单
            }
        });
    };
    /**
     * 全屏 & 取消全屏
     */
    let initFullScreen = function () {
        /**
         * 全屏
         *
         * @param element {object} 要全屏的元素
         */
        function launchFullScreen(element) {
            if (element.requestFullscreen) {
                element.requestFullscreen();
            } else if (element.mozRequestFullScreen) {
                element.mozRequestFullScreen();
            } else if (element.webkitRequestFullscreen) {
                element.webkitRequestFullscreen();
            } else if (element.msRequestFullscreen) {
                element.msRequestFullscreen();
            }
        }

        /**
         * 退出全屏
         */
        function exitFullscreen() {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            }
        }

        let icon = {
            full: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <polygon id="Bound" points="0 0 24 0 24 24 0 24"/>\n' +
                '        <path d="M6,18 L9,18 C9.66666667,18.1143819 10,18.4477153 10,19 C10,19.5522847 9.66666667,19.8856181 9,20 L4,20 L4,15 C4,14.3333333 4.33333333,14 5,14 C5.66666667,14 6,14.3333333 6,15 L6,18 Z M18,18 L18,15 C18.1143819,14.3333333 18.4477153,14 19,14 C19.5522847,14 19.8856181,14.3333333 20,15 L20,20 L15,20 C14.3333333,20 14,19.6666667 14,19 C14,18.3333333 14.3333333,18 15,18 L18,18 Z M18,6 L15,6 C14.3333333,5.88561808 14,5.55228475 14,5 C14,4.44771525 14.3333333,4.11438192 15,4 L20,4 L20,9 C20,9.66666667 19.6666667,10 19,10 C18.3333333,10 18,9.66666667 18,9 L18,6 Z M6,6 L6,9 C5.88561808,9.66666667 5.55228475,10 5,10 C4.44771525,10 4.11438192,9.66666667 4,9 L4,4 L9,4 C9.66666667,4 10,4.33333333 10,5 C10,5.66666667 9.66666667,6 9,6 L6,6 Z" id="Combined-Shape" fill="#000000" fill-rule="nonzero"/>\n' +
                '    </g>\n' +
                '</svg>',
            cancel: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <polygon id="Bound" points="0 0 24 0 24 24 0 24"/>\n' +
                '        <path d="M10,14 L5,14 C4.33333333,13.8856181 4,13.5522847 4,13 C4,12.4477153 4.33333333,12.1143819 5,12 L12,12 L12,19 C12,19.6666667 11.6666667,20 11,20 C10.3333333,20 10,19.6666667 10,19 L10,14 Z M15,9 L20,9 C20.6666667,9.11438192 21,9.44771525 21,10 C21,10.5522847 20.6666667,10.8856181 20,11 L13,11 L13,4 C13,3.33333333 13.3333333,3 14,3 C14.6666667,3 15,3.33333333 15,4 L15,9 Z" id="Combined-Shape" fill="#000000" fill-rule="nonzero"/>\n' +
                '        <path d="M3.87867966,18.7071068 L6.70710678,15.8786797 C7.09763107,15.4881554 7.73079605,15.4881554 8.12132034,15.8786797 C8.51184464,16.2692039 8.51184464,16.9023689 8.12132034,17.2928932 L5.29289322,20.1213203 C4.90236893,20.5118446 4.26920395,20.5118446 3.87867966,20.1213203 C3.48815536,19.7307961 3.48815536,19.0976311 3.87867966,18.7071068 Z M16.8786797,5.70710678 L19.7071068,2.87867966 C20.0976311,2.48815536 20.7307961,2.48815536 21.1213203,2.87867966 C21.5118446,3.26920395 21.5118446,3.90236893 21.1213203,4.29289322 L18.2928932,7.12132034 C17.9023689,7.51184464 17.2692039,7.51184464 16.8786797,7.12132034 C16.4881554,6.73079605 16.4881554,6.09763107 16.8786797,5.70710678 Z" id="Combined-Shape" fill="#000000" opacity="0.3"/>\n' +
                '    </g>\n' +
                '</svg>'
        };
        $('#full-screen').click(function () {
            let isFullScreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement
            if (isFullScreen) {
                $(this).find('.e-header-topbar-icon').html(icon.full);
                exitFullscreen();
            } else {
                $(this).find('.e-header-topbar-icon').html(icon.cancel);
                launchFullScreen(document.documentElement);
            }
        });
    };
    /**
     * 回到顶部
     */
    let initScrollTop = function () {
        /**
         * 处理回到顶部按钮显示/隐藏
         */
        let handle = function(){
            let pos = window.pageYOffset;
            if (pos > 300) {
                $body.addClass('e-scroll-top--on');
            } else {
                $body.removeClass('e-scroll-top--on');
            }
        };
        // 窗口滚动事件
        if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
            window.addEventListener('touchend', function() {
                handle();
            });

            window.addEventListener('touchcancel', function() {
                handle();
            });

            window.addEventListener('touchleave', function() {
                handle();
            });
        } else {
            window.addEventListener('scroll', function() {
                handle();
            });
        }
        // 绑定回到顶部事件
        $('#scroll-top').click(function () {
            EUtil.scrollTo('html,body', 0, 500);
        });
    };

    /**
     * 菜单回调
     */
    let menuCallback = function () {
        /**
         * 批量关闭标签页
         *
         * @param element {object} 标签页
         * @param type {string|null} closeOtherTabs/closeAllTabs/closeLeftTabs/closeRightTabs
         */
        let batchCloseTabs = function (element, type) {
            let $currentLi = $(element).parent();
            let $tabs = null;
            switch (type) {
                case 'closeOtherTabs':
                    // 获取除当前选中标签页以外的所有标签页
                    $currentLi.addClass('current-chose');
                    $tabs = $currentLi.parent().children(':not(.current-chose)');
                    $currentLi.removeClass('current-chose');
                    break;
                case 'closeAllTabs':
                    $tabs = $currentLi.parent().children();
                    break;
                case 'closeLeftTabs':
                    $tabs = $currentLi.prevAll();
                    break;
                case 'closeRightTabs':
                    $tabs = $currentLi.nextAll();
                    break;
                default:
                    // 默认关闭当前
                    $tabs = $currentLi;
            }
            $tabs.find('.tab-close').click();
            tabPage.toActiveTab();
        };

        /**
         * 刷新当前
         *
         * @param element {object} 标签页
         * @param key {string} 菜单key
         * @param opt {object} option
         */
        let refreshCurrentTabs = function (element, key, opt) {
            tabPage.refresh($(element).data('url'));
        };
        /**
         * 在新页面打开
         *
         * @param element {object} 标签页
         * @param key {string} 菜单key
         * @param opt {object} option
         */
        let cardingOnTheNewTab = function (element, key, opt) {
            window.open($(element).data('url'));
        };

        return {
            /**
             * 关闭当前
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            closeCurrentTab: function (key, opt) {
                batchCloseTabs(this, null);
            },
            /**
             * 关闭其他
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            closeOtherTabs: function (key, opt) {
                batchCloseTabs(this, 'closeOtherTabs');
            },
            /**
             * 关闭全部
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            closeAllTabs: function (key, opt) {
                batchCloseTabs(this, 'closeAllTabs');
            },
            /**
             * 关闭左边
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            closeLeftTabs: function (key, opt) {
                batchCloseTabs(this, 'closeLeftTabs');
            },
            /**
             * 关闭右边
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            closeRightTabs: function (key, opt) {
                batchCloseTabs(this, 'closeRightTabs');
            },
            /**
             * 刷新当前
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            refreshCurrentTabs: function (key, opt) {
                refreshCurrentTabs(this, key, opt)
            },
            /**
             * 在新标签页打开
             *
             * @param key {string} 菜单key
             * @param opt {object} option
             */
            cardingOnTheNewTab: function (key, opt) {
                cardingOnTheNewTab(this, key, opt);
            }
        }
    }();

    /**
     * 初始化标签页右键菜单
     */
    let initTabsRightMenu = function () {
        // 右键菜单绑定元素
        let defaultSelector = '.tab-page .con-tabs a.tab';
        /**
         * 菜单节点图标
         */
        let icon = {
            close: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <g id="Group" transform="translate(12.000000, 12.000000) rotate(-45.000000) translate(-12.000000, -12.000000) translate(4.000000, 4.000000)" fill="#000000">\n' +
                '            <rect id="Rectangle-185" x="0" y="7" width="16" height="2" rx="1"/>\n' +
                '            <rect id="Rectangle-185-Copy" opacity="0.3" transform="translate(8.000000, 8.000000) rotate(-270.000000) translate(-8.000000, -8.000000) " x="0" y="7" width="16" height="2" rx="1"/>\n' +
                '        </g>\n' +
                '    </g>\n' +
                '</svg>',
            left: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <polygon id="Shape" points="0 0 24 0 24 24 0 24"/>\n' +
                '        <rect id="Rectangle" fill="#000000" opacity="0.3" transform="translate(14.000000, 12.000000) scale(-1, 1) rotate(-90.000000) translate(-14.000000, -12.000000) " x="13" y="5" width="2" height="14" rx="1"/>\n' +
                '        <rect id="Rectangle-199-Copy" fill="#000000" opacity="0.3" x="3" y="3" width="2" height="18" rx="1"/>\n' +
                '        <path d="M5.7071045,15.7071045 C5.3165802,16.0976288 4.68341522,16.0976288 4.29289093,15.7071045 C3.90236664,15.3165802 3.90236664,14.6834152 4.29289093,14.2928909 L10.2928909,8.29289093 C10.6714699,7.914312 11.2810563,7.90106637 11.6757223,8.26284357 L17.6757223,13.7628436 C18.0828413,14.136036 18.1103443,14.7686034 17.7371519,15.1757223 C17.3639594,15.5828413 16.7313921,15.6103443 16.3242731,15.2371519 L11.0300735,10.3841355 L5.7071045,15.7071045 Z" id="Path-94" fill="#000000" fill-rule="nonzero" transform="translate(11.000001, 11.999997) scale(-1, -1) rotate(90.000000) translate(-11.000001, -11.999997) "/>\n' +
                '    </g>\n' +
                '</svg>',
            right: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <polygon id="Shape" points="0 0 24 0 24 24 0 24"/>\n' +
                '        <rect id="Rectangle" fill="#000000" opacity="0.3" transform="translate(14.000000, 12.000000) rotate(-90.000000) translate(-14.000000, -12.000000) " x="13" y="5" width="2" height="14" rx="1"/>\n' +
                '        <rect id="Rectangle-199-Copy" fill="#000000" opacity="0.3" x="3" y="3" width="2" height="18" rx="1"/>\n' +
                '        <path d="M11.7071032,15.7071045 C11.3165789,16.0976288 10.6834139,16.0976288 10.2928896,15.7071045 C9.90236532,15.3165802 9.90236532,14.6834152 10.2928896,14.2928909 L16.2928896,8.29289093 C16.6714686,7.914312 17.281055,7.90106637 17.675721,8.26284357 L23.675721,13.7628436 C24.08284,14.136036 24.1103429,14.7686034 23.7371505,15.1757223 C23.3639581,15.5828413 22.7313908,15.6103443 22.3242718,15.2371519 L17.0300721,10.3841355 L11.7071032,15.7071045 Z" id="Path-94" fill="#000000" fill-rule="nonzero" transform="translate(16.999999, 11.999997) scale(1, -1) rotate(90.000000) translate(-16.999999, -11.999997) "/>\n' +
                '    </g>\n' +
                '</svg>',
            refresh: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <rect id="bound" x="0" y="0" width="24" height="24"/>\n' +
                '        <path d="M8.43296491,7.17429118 L9.40782327,7.85689436 C9.49616631,7.91875282 9.56214077,8.00751728 9.5959027,8.10994332 C9.68235021,8.37220548 9.53982427,8.65489052 9.27756211,8.74133803 L5.89079566,9.85769242 C5.84469033,9.87288977 5.79661753,9.8812917 5.74809064,9.88263369 C5.4720538,9.8902674 5.24209339,9.67268366 5.23445968,9.39664682 L5.13610134,5.83998177 C5.13313425,5.73269078 5.16477113,5.62729274 5.22633424,5.53937151 C5.384723,5.31316892 5.69649589,5.25819495 5.92269848,5.4165837 L6.72910242,5.98123382 C8.16546398,4.72182424 10.0239806,4 12,4 C16.418278,4 20,7.581722 20,12 C20,16.418278 16.418278,20 12,20 C7.581722,20 4,16.418278 4,12 L6,12 C6,15.3137085 8.6862915,18 12,18 C15.3137085,18 18,15.3137085 18,12 C18,8.6862915 15.3137085,6 12,6 C10.6885336,6 9.44767246,6.42282109 8.43296491,7.17429118 Z" id="Combined-Shape" fill="#000000" fill-rule="nonzero"/>\n' +
                '    </g>\n' +
                '</svg>',
            open: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" class="e-svg-icon">\n' +
                '    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">\n' +
                '        <rect id="bound" x="0" y="0" width="24" height="24"/>\n' +
                '        <path d="M10.9,2 C11.4522847,2 11.9,2.44771525 11.9,3 C11.9,3.55228475 11.4522847,4 10.9,4 L6,4 C4.8954305,4 4,4.8954305 4,6 L4,18 C4,19.1045695 4.8954305,20 6,20 L18,20 C19.1045695,20 20,19.1045695 20,18 L20,16 C20,15.4477153 20.4477153,15 21,15 C21.5522847,15 22,15.4477153 22,16 L22,18 C22,20.209139 20.209139,22 18,22 L6,22 C3.790861,22 2,20.209139 2,18 L2,6 C2,3.790861 3.790861,2 6,2 L10.9,2 Z" id="Path-57" fill="#000000" fill-rule="nonzero" opacity="0.3"/>\n' +
                '        <path d="M24.0690576,13.8973499 C24.0690576,13.1346331 24.2324969,10.1246259 21.8580869,7.73659596 C20.2600137,6.12944276 17.8683518,5.85068794 15.0081639,5.72356847 L15.0081639,1.83791555 C15.0081639,1.42370199 14.6723775,1.08791555 14.2581639,1.08791555 C14.0718537,1.08791555 13.892213,1.15726043 13.7542266,1.28244533 L7.24606818,7.18681951 C6.93929045,7.46513642 6.9162184,7.93944934 7.1945353,8.24622707 C7.20914339,8.26232899 7.22444472,8.27778811 7.24039592,8.29256062 L13.7485543,14.3198102 C14.0524605,14.6012598 14.5269852,14.5830551 14.8084348,14.2791489 C14.9368329,14.140506 15.0081639,13.9585047 15.0081639,13.7695393 L15.0081639,9.90761477 C16.8241562,9.95755456 18.1177196,10.0730665 19.2929978,10.4469645 C20.9778605,10.9829796 22.2816185,12.4994368 23.2042718,14.996336 L23.2043032,14.9963244 C23.313119,15.2908036 23.5938372,15.4863432 23.9077781,15.4863432 L24.0735976,15.4863432 C24.0735976,15.0278051 24.0690576,14.3014082 24.0690576,13.8973499 Z" id="Shape" fill="#000000" fill-rule="nonzero" transform="translate(15.536799, 8.287129) scale(-1, 1) translate(-15.536799, -8.287129) "/>\n' +
                '    </g>\n' +
                '</svg>'
        };
        /**
         * 菜单节点
         */
        let items = {
            closeCurrentTab: {
                name: '关闭当前',
                icon: icon.close,
                type: 'format',
                disabled: checkMenu,
                callback: menuCallback.closeCurrentTab
            },
            closeOtherTabs: {
                name: '关闭其他',
                icon: icon.close,
                type: 'format',
                disabled: checkMenu,
                callback: menuCallback.closeOtherTabs
            },
            closeLeftTabs: {
                name: '关闭左侧',
                icon: icon.left,
                type: 'format',
                disabled: checkMenu,
                callback: menuCallback.closeLeftTabs
            },
            closeRightTabs: {
                name: '关闭右侧',
                icon: icon.right,
                type: 'format',
                disabled: checkMenu,
                callback: menuCallback.closeRightTabs
            },
            closeAllTabs: {
                name: '全部关闭',
                icon: icon.close,
                type: 'format',
                disabled: checkMenu,
                callback: menuCallback.closeAllTabs
            },
            sep1: '---------',
            refreshCurrentTabs: {
                name: '刷新页面',
                icon: icon.refresh,
                type: 'format',
                callback: menuCallback.refreshCurrentTabs
            },
            cardingOnTheNewTab: {
                name: '在新标签页打开',
                icon: icon.open,
                type: 'format',
                callback: menuCallback.cardingOnTheNewTab
            }
        };

        /**
         * 检查标签页是否可关闭
         *
         * @param $tab {object} 标签页
         * @return {true/false}
         */
        function checkCanClose($tab) {
            return $tab.find('.tab-close').length;
        }

        /**
         * 检查菜单节点是否 disabled
         *
         * @param key {string} 菜单节点key
         */
        function checkMenu(key) {
            let $currentLi = $(this).parent();
            let canClose = false;
            if ('closeCurrentTab' === key) {
                let tabClose = $(this).find('.tab-close');
                return !tabClose.length;
            } else if ('closeRightTabs' === key || 'closeLeftTabs' === key) {
                // 往左/右一个一个查找标签页是否可关闭,如遇到可关闭的则返回false
                let hasRightTab = true;
                let isCloseRightTabs = 'closeRightTabs' === key;
                while (hasRightTab) {
                    let next = null;
                    isCloseRightTabs ? next = $currentLi.next() : next = $currentLi.prev();
                    $currentLi = next;
                    if (!next.length) {
                        hasRightTab = false;
                    }
                    if (checkCanClose(next)) {
                        return false;
                    }
                }
                return true;
            } else if ('closeOtherTabs' === key || 'closeAllTabs' === key) {
                let isCloseOtherTabs = 'closeOtherTabs' === key;
                if (isCloseOtherTabs) {
                    // 如果是关闭其他,排除当前选中tab
                    $currentLi.addClass('current-chose');
                }
                let $tabs = $currentLi.parent().children(':not(.current-chose)');
                if (isCloseOtherTabs) {
                    $currentLi.removeClass('current-chose');
                }
                $($tabs).each(function (index, li) {
                    // 可以关闭
                    if (checkCanClose($(li))) {
                        canClose = true;
                        return;
                    }
                });
                return !canClose;
            }
            return false;
        }

        /**
         * 菜单节点格式化
         *
         * @param item {object} 节点属性
         * @param opt {object} 菜单属性
         * @param root
         */
        $.contextMenu.types.format = function (item, opt, root) {
            $('<a class="dropdown-item" href="#">' + item.icon + item.name + '</a>').appendTo(this);
        };

        $.contextMenu({
            selector: defaultSelector,
            items: items,
            classNames: {
                // 自定义菜单节点class
                hover: '-hover',
                visible: '-visible'
            }
        });
    };

    return {
        initHeader: function () {
            // 移动端模式header-topbar显示 & 隐藏
            initMobileHeaderToggle();
            // 全屏
            initFullScreen();
            // 初始化标签页
            EApp.initTabs();
            // 初始化标签页右键菜单
            initTabsRightMenu();
        },
        initAside: function () {
            // 左侧边
            initMobileAsideToggler();
            // pc模式切换菜单展开 & 收起
            initAsideToggler();
            // 初始化侧边菜单
            initAsideMenu();
        },
        // 初始化
        init: function () {
            if ($body.hasClass('e-root-page')) {
                ELayout.initHeader();
                ELayout.initAside();
            }
            initScrollTop();
        },
        /**
         * 获取侧边菜单对象
         *
         * @returns {*}
         */
        getAsideMenu: function () {
            return asideMenu;
        }
    }
}();
// 初始化
$(document).ready(function () {
    ELayout.init();
});


